Executing prepared SQL in function without loop returning a table - sql

I have this source here:
CREATE OR REPLACE FUNCTION SWITCHTEST (CHOICE VARCHAR(10))
RETURNS TABLE ( R_COL1 VARCHAR(1024) ,R_COL2 VARCHAR(1024) )
LANGUAGE SQL
MODIFIES SQL DATA
BEGIN
DECLARE SQLSTATE CHAR(5);
DECLARE SELECT1 VARCHAR(1024);
DECLARE L_COL1 VARCHAR(1024);
DECLARE L_COL2 VARCHAR(1024);
SET SELECT1 = 'SELECT TEST, DESCR FROM TESTTAB FETCH FIRST 10 ROWS ONLY';
SET SELECT2 = 'SELECT DESCR, COLOUR FROM TESTTAB FETCH FIRST 10 ROWS ONLY';
IF CHOICE = 'FIRST' THEN
PREPARE S1 FROM SELECT1;
ELSEIF CHOICE = 'SECOND' THEN
PREPARE S1 FROM SELECT2;
ELSE
END IF;
RETURN EXEC(S1);
END#
Calling it like
SELECT * FROM TABLE (SWITCHTEST('FIRST')) #
It should just execute the SQL in the S1 prepared statement and I don't want to use a loop in the function.
I am running DB2 Windows 10.5
Any ideas on how to fix this?
I know that the EXEC(S1) is wrong but I can't find anything on the IBM page that shows how to make this work.
Thank you for your help.
Viking

Using intermediate CREATED GLOBAL TEMPORARY TABLE.
--#SET TERMINATOR #
CREATE GLOBAL TEMPORARY TABLE SWITCHTEST ( R_COL1 VARCHAR(1024) ,R_COL2 VARCHAR(1024)) ON COMMIT PRESERVE ROWS NOT LOGGED#
CREATE OR REPLACE FUNCTION SWITCHTEST (CHOICE VARCHAR(10))
RETURNS TABLE (R_COL1 VARCHAR(1024), R_COL2 VARCHAR(1024) )
LANGUAGE SQL
MODIFIES SQL DATA
BEGIN ATOMIC
DELETE FROM SWITCHTEST;
IF CHOICE='FIRST' THEN
INSERT INTO SWITCHTEST SELECT TEST, DESCR FROM TESTTAB FETCH FIRST 10 ROWS ONLY;
ELSEIF CHOICE='SECOND' THEN
INSERT INTO SWITCHTEST SELECT DESCR, COLOUR FROM TESTTAB FETCH FIRST 10 ROWS ONLY;
END IF;
RETURN SELECT R_COL1, R_COL2 FROM SWITCHTEST;
END#

Not sure if it fits your needs, but you can use a case expression to choose columns for the result table. I don't think fetch first is supported in functions, but you can add that in the call:
CREATE OR REPLACE FUNCTION SWITCHTEST (CHOICE VARCHAR(10))
RETURNS TABLE ( R_COL1 VARCHAR(1024) ,R_COL2 VARCHAR(1024) )
LANGUAGE SQL
READS SQL DATA
RETURN
SELECT CASE CHOICE WHEN 'FIRST' THEN TEST ELSE DESCR END
, CASE CHOICE WHEN 'FIRST' THEN DESCR ELSE COLOUR END
FROM TESTTAB ;
SELECT * FROM TABLE (SWITCHTEST('FIRST'))
FETCH FIRST 10 ROWS ONLY;

Related

How can I pass a list, array or string to be separated as a parameter to redshift

I'm trying to write a simple query with an in clause like so:
SELECT *
FROM storeupcsalesbyday
WHERE date >= '9/1/2020' AND date <= '9/10/2020' AND upc in ('0000000004011', '0000000094011')
I need to be able to pass the values in the in clause as a parameter, the number of values in the in clause are variable and could be one or thousands depending on the user input. In other sql databases I have solved this problem by creating a user defined function that takes a string, splits it on a delimiter and inserts the values in a temp table, then I would select all from the temp table to use in my in clause. However user defined functions in redshift do not allow tables as a return type. How are others solving this problem in redshift.
Thanks
I was able to create a stored procedure that takes a varchar and creates a temp table of all "slices" of the varchar broken up by a delimiter (in this case a ','). I just wanted to share it here in case someone else has this issue.
Here is the procedure:
CREATE OR REPLACE Procedure sp_UPCStringToTempTable(upcList IN varchar(max))
AS 'DECLARE
idx int;
slice varchar(8000);
upcListVar varchar(max);
BEGIN
idx = 1;
upcListVar = upcList;
DROP TABLE if exists tmp_upc;
CREATE TEMP TABLE tmp_upc(upc varchar(14));
WHILE idx != 0 LOOP
idx = charindex('','', upcListVar);
IF idx != 0 THEN
slice = left(upcListVar, idx - 1);
END IF;
IF idx = 0 THEN
slice = upcListVar;
END IF;
IF len(slice) > 0 THEN
INSERT INTO tmp_upc values (slice);
END IF;
upcListVar = right(upcListVar, len(upcListVar) - idx);
END LOOP;
END;
' LANGUAGE plpgsql;
create table num(id int) ;
insert into num values(1), (2),(3);
with t as
(
select split_part('0000000004011, 0000000094011',',',id ) col1 from num
)
select * from a join t on a.col1 = t.col1
This should solve your problem.

Store result of a query inside a function

I've the following function:
DO
$do$
DECLARE
maxgid integer;
tableloop integer;
obstacle geometry;
simplifyedobstacle geometry;
BEGIN
select max(gid) from public.terrain_obstacle_temp into maxgid;
FOR tableloop IN 1 .. maxgid
LOOP
insert into public.terrain_obstacle (tse_coll,tse_height,geom) select tse_coll,tse_height,geom
from public.terrain_obstacle_temp where gid = tableloop;
END LOOP;
END
$do$;
I need to modify this function in order to execute different queries according to the type of a column of public.terrain_obstacle_temp.
This is a temporary table created by reading a shapefile, and I need to know the kind of the geom column of that table. I have a query that give the data to me:
SELECT type
FROM geometry_columns
WHERE f_table_schema = 'public'
AND f_table_name = 'terrain_obstacle'
and f_geometry_column = 'geom';
It returns me a character_varying value (in this case MULTIPOLYGON).
Ho can I modify the function in order to get the result of the query, and create an if statement that allows me to execute some code according to the result of that query?
Is the intention to copy all the records from the temp table to the actual table? If so, you may be able to skip the loop:
insert into public.terrain_obstacle (tse_coll, tse_height, geom)
select tse_coll, tse_height, geom
from public.terrain_obstacle_temp
;
Do terrain_obstacle and terrain_obstacle_temp have the same structure? If so, then the "insert into ... select ..." should work fine provided the column types are the same.
If conditional typing is required, use the CASE WHEN syntax:
v_type geometry_columns.type%TYPE;
...
SELECT type
INTO v_type
FROM geometry_columns
WHERE f_table_schema = 'public'
AND f_table_name = 'terrain_obstacle'
AND f_geometry_column = 'geom'
;
insert into public.terrain_obstacle (tse_coll, tse_height, geom)
select tse_coll
,tse_height
,CASE WHEN v_type = 'MULTIPOLYGON' THEN my_func1(geom)
WHEN v_type = 'POINT' THEN my_func2(geom)
ELSE my_default(geom)
END
from public.terrain_obstacle_temp
;

nzsql - Converting a subquery into columns for another select

Goal: Use a given subquery's results (a single column with many rows of names) to act as the outer select's selection field.
Currently, my subquery is the following:
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'test_table' AND column_name not in ('colRemove');
What I am doing in this subquery is grabbing all the column names from a table (i.e. test_table) and outputting all except for the column name specified (i.e. colRemove). As stated in the "goal", I want to use this subquery as such:
SELECT (*enter subquery from above here*)
FROM actual_table
WHERE (*enter specific conditions*)
I am working on a Netezza SQL server that is version 7.0.4.4. Ideally, I would like to make the entire query executable in one line, but for now, a working solution would be much appreciated. Thanks!
Note: I do not believe that the SQL extensions has been installed (i.e. arrays), but I will need to double check this.
A year too late, here's the best I can come up with but, as you already noticed, it requires a stored procedure to do the dynamic SQL. The stored proc creates a view with the all the columns from the source table minus the one you want to exclude.
-- Create test data.
CREATE TABLE test (firstcol INTEGER, secondcol INTEGER, thirdcol INTEGER);
INSERT INTO test (firstcol, secondcol, thirdcol) VALUES (1, 2, 3);
INSERT INTO test (firstcol, secondcol, thirdcol) VALUES (4, 5, 6);
-- Install stored procedure.
CREATE OR REPLACE PROCEDURE CreateLimitedView (varchar(ANY), varchar(ANY)) RETURNS BOOLEAN
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
tableName ALIAS FOR $1;
columnToExclude ALIAS FOR $2;
colRec RECORD;
cols VARCHAR(2000); -- Adjust as needed.
isfirstcol BOOLEAN;
BEGIN
isfirstcol := true;
FOR colRec IN EXECUTE
'SELECT ATTNAME AS NAME FROM _V_RELATION_COLUMN
WHERE
NAME=UPPER('||quote_literal(tableName)||')
AND ATTNAME <> UPPER('||quote_literal(columnToExclude)||')
ORDER BY ATTNUM'
LOOP
IF isfirstcol THEN
cols := colRec.NAME;
ELSE
cols := cols || ', ' || colRec.NAME;
END IF;
isfirstcol := false;
END LOOP;
-- Should really check if 'LimitedView' already exists as a view, table or synonym.
EXECUTE IMMEDIATE 'CREATE OR REPLACE VIEW LimitedView AS SELECT ' || cols || ' FROM ' || quote_ident(tableName);
RETURN true;
END;
END_PROC
;
-- Run the stored proc to create the view.
CALL CreateLimitedView('test', 'secondcol');
-- Select results from the view.
SELECT * FROM limitedView WHERE firstcol = 4;
FIRSTCOL | THIRDCOL
----------+----------
4 | 6
You could have the stored proc return a resultset directly but then you wouldn't be able to filter results with a WHERE clause.

T-SQL equivalent of Oracle's collection methods (first, last, next)?

We're moving from Oracle to SQL Server and I'm converting a query from a table variable populated with a BULK COLLECT INTO query. I'm thinking of using a cursor (definitely open to other suggestions), but in the Oracle code that processes the query it's using Table_var.FIRST .NEXT and .LAST. Here's some sample code of how it's using these. It appears that the first/next/last are giving indexes into the table var's records.
TYPE Pers_DOB_LastInitial IS RECORD (
Person_ID Person.Person_ID%TYPE,
DOB Person.Birthdate%TYPE,
LastInitial VARCHAR2(1)
);
TYPE Dup_Table IS TABLE OF Pers_DOB_LastInitial INDEX BY BINARY_INTEGER;
Dup_Tab Dup_Table;
and a function that uses these types:
FUNCTION Last_In_Group( pStart NUMBER, pDOB Person.Birthdate%TYPE, pLastInitial VARCHAR2 )
RETURN NUMBER IS
vResult NUMBER;
BEGIN
IF pStart = Dup_Tab.LAST THEN
RETURN pStart;
END IF;
vResult := pStart;
FOR vIndex IN pStart .. Dup_Tab.LAST LOOP
IF Dup_Tab.EXISTS( vIndex ) THEN
IF Dup_Tab( vIndex ).DOB = pDOB AND Dup_Tab( vIndex ).LastInitial = pLastInitial THEN
vResult := vIndex;
ELSE
EXIT;
END IF;
END IF;
END LOOP;
RETURN vResult;
END Last_In_Group;
I don't need the coding done for me, just need to be pointed in the right direction. I'm thinking of using a cursor, but the only thing I'm familiar with is simply fetching the next record from a cursor in T-SQL and want to see if there are equivalent ways to reference row indexes for cursors (or temp tables).
EDIT: I've just discovered the following and am looking into it. Definitely open to hints on whether this is a good path to pursue or if cursors are still better.
http://www.sql-server-performance.com/2004/operations-no-cursors/2/
DECLARE #dupTab TABLE (
person_id numeric(8,0),
DOB date,
LastInitial char(1)
)
INSERT #dupTab
SELECT ...
Not 100% sure what you're asking, but have you looked at the new LEAD, LAG, FIRST_VALUE, and LAST_VALUE keywords in Sql Server 2012?
Assuming your table is called Dup_Tab, and #DOB and #LastInitial are variables you passed into your function, might this work?
select PersonID from(
select
rank = row_number() over (order by DOB DESC, LastInitial DESC),
PersonID
from dup_tab where DOB=#DOB and LastInitial=#LastInitial
) subgroup
where rank=1
It's just using the windowed Rank() function to assign each record a row number based on DOB/LastInitial ordered last-first and then you select the row with rank = 1, giving you the last record in the set of persons with a specific DOB and LastInitial.
I translated your sample code into it's SQL-Server counterpart. If you want to do row-by-row processing the best performing way is imo to use a FAST_FORWARD cursor.
code:
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[Last_In_Group]') AND xtype in (N'FN', N'IF', N'TF'))
drop function Last_In_Group
go
create function Last_In_Group(#pStart int, #pDOB date, #pLastInitial varchar(1))
returns int
AS
BEGIN
DECLARE #Dup_Tab TABLE(
Person_ID int,
DOB date,
LastInitial varchar(1)
)
-- populate the table with some data
insert #Dup_Tab values
(1, '31.12.2013', 'P'), (2, '24.12.2013', 'C'), (3, '24.12.2013', 'C')
declare #vResult int = 0,
#cDOB date,
#cLastInitial varchar(1)
if(#vResult = #pStart) return #pStart
set #vResult = #pStart
-- loop over your data with a fast cursor
declare cur cursor FAST_FORWARD for select DOB, LastInitial from #Dup_Tab
open cur
fetch next from cur
into #cDOB, #cLastInitial
while ##FETCH_STATUS = 0
begin
if(#cDOB = #pDOB and #cLastInitial = #pLastInitial)
set #vResult += 1
fetch next from cur
into #cDOB, #cLastInitial
end
return #vResult;
end
go
print dbo.Last_In_Group(1, '24.12.2013', 'C')
go
It might not work as your sample above but I hope this gives you some hint where to go further.

How do I check if a temporary table exists in SQL Anywhere?

I want to write a SQL IF statement that checks whether or not a local temporary table exists, but those kinds of tables are not recorded in the SQL Anywhere system catalog.
Note that you can do this in 11.0.1 and higher:
DROP TABLE IF EXISTS t;
If you're asking the question, "How do I drop a local temporary table without raising an error if it doesn't exist?" then the answer's simple: just DROP it and ignore any error:
BEGIN
DROP TABLE t;
EXCEPTION WHEN OTHERS THEN
END;
If you really need to know the answer to the question "Does table t exist?" you can query the table and analyze the resulting SQLSTATE. The following function makes use of several features:
ON EXCEPTION RESUME ignores any exception raised by the SELECT and passes control to the IF statement.
EXECUTE IMMEDIATE lets you write a query where the table name is in a string variable.
TOP 1 makes sure that only one row is selected even if the table contains a million rows.
ORDER BY 1 lets you meet the requirement that TOP can only be used when the result set is ordered.
SELECT 1 relieves you of the burden of specifying a column name.
INTO #dummy means the SELECT (and consequently the EXECUTE IMMEDIATE) doesn't return a result set.
If the SELECT works, it's either going to set SQLSTATE to '00000' for success or '02000' for row not found. Any other SQLSTATE means there's some serious problem with the table... like it doesn't exist.
CREATE FUNCTION f_table_is_ok
( IN #table_name VARCHAR ( 128 ) )
RETURNS INTEGER
ON EXCEPTION RESUME
BEGIN
DECLARE #dummy INTEGER;
EXECUTE IMMEDIATE STRING (
'SELECT TOP 1 1 INTO #dummy FROM ',
#table_name,
' ORDER BY 1' );
IF SQLSTATE IN ( '00000', '02000' ) THEN
RETURN 1
ELSE
RETURN 0
END IF;
END;
Here's some test code:
BEGIN
DECLARE LOCAL TEMPORARY TABLE tt ( c INTEGER );
DECLARE LOCAL TEMPORARY TABLE "t t" ( c INTEGER );
SELECT f_table_is_ok ( 'asdf' );
SELECT f_table_is_ok ( 'tt' );
SELECT f_table_is_ok ( '"t t"' );
SELECT f_table_is_ok ( '"SYS"."SYSTABLE"' );
END;
just try to drop it anyways and ignore the error...
BEGIN
DROP TABLE table;
EXCEPTION WHEN OTHERS THEN
END;