Syntax error in defining a stored procedure in DB2 - db2

I'm defining a simple stored procedure in DB2 as follows but it gives me syntax error
CREATE OR REPLACE PROCEDURE Schema1.TESTSP1 ()
DYNAMIC RESULT SETS 1
P1: BEGIN
if( exists(
select 1 from syscat.tables where tabschema = 'Schema' and tabname = 'SPTEST'
)) then
drop table Schema.SPTEST ;
create table Schema.SPTEST as
(select * from Schema.XYZ) WITH DATA ;
end if;
END P1
What is wrong here?

You need to study the IBM example stored procedures for SQL PL.
Get the sample procedures working in your environment to build knowledge and skills.
You need to understand the difference between dynamic-sql and static-SQL and when to use either one.
You need to add exception handlers for any exceptions.
As you specified that it returns one result-set you need to open a cursor on that final result-set.
The examples below are for the Db2-LUW Version 11.1.3.3 CLP (command line processor), i.e. on Microsoft Windows that is the what runs when you open db2cwadmin.bat, and on Linux/Unix the bash/ksh shell:
Note: the WITH DATA requires Db2-LUW 11.1.3.3, older versions supported only WITH NO DATA. If you cannot upgrade, then use instead 'CREATE TABLE ... LIKE ...' followed by a separate statement INSERT INTO ... SELECT ...FROM ...
Here is a skeleton resembling your example, using static SQL on Db2-LUW V11.1.3.3:
--#SET TERMINATOR #
CREATE OR REPLACE PROCEDURE Schema1.TESTSP1 ()
DYNAMIC RESULT SETS 1
P1: BEGIN
DECLARE SQLCODE integer;
DECLARE table_exists integer default 0;
select 1 into table_exists from syscat.tables where tabschema = 'SOMESCHEMA' and tabname = 'SPTEST';
if table_exists = 1
then
drop table SOMESCHEMA.SPTEST ;
create table SOMESCHEMA.SPTEST as (select * from SCHEMA.SPTEST ) with data ;
end if;
END P1
#
Here is a skeleton which resembles your example but uses dynamic-SQL on Db2-LUW V11.1.3.3, which would be useful if you don't know in advance the table/column names and they are available in parameters to the stored procedure (not shown) in which case you can dynamically build the SQL and execute it:
--#SET TERMINATOR #
CREATE OR REPLACE PROCEDURE Schema1.TESTSP1 ()
DYNAMIC RESULT SETS 1
P1: BEGIN
DECLARE SQLCODE integer;
DECLARE table_exists integer default 0;
select 1 into table_exists from syscat.tables where tabschema = 'SOMESCHEMA' and tabname = 'SPTEST';
if table_exists = 1
then
execute immediate('drop table SOMESCHEMA.SPTEST') ;
execute immediate('create table SOMESCHEMA.SPTEST as (select * from SCHEMA.SPTEST ) with data') ;
end if;
END P1
#

Related

table creation procedure DB2

I have created a procedure like the one below. i know PL SQL a bit but i am new to DB2.
when i execute the below procedure getting an error that the use of reserve keyoword end is invalid.
CREATE PROCEDURE test()
LANGUAGE SQL
DYNAMIC RESULT SETS 1
WLM ENVIRONMENT FOR DEBUG MODE #TTCOM
ASUTIME NO LIMIT
BEGIN
DECLARE SQLSTATE CHAR(5) DEFAULT '00000';
DECLARE SQLCODE INTEGER DEFAULT 0;
DECLARE TABLE_DOES_NOT_EXIST_STATE CONDITION FOR SQLSTATE '42704';
DECLARE tname VARCHAR(5000);
DECLARE CONTINUE HANDLER FOR TABLE_DOES_NOT_EXIST_STATE BEGIN
END;
FOR i AS SELECT tablename FROM EWPS.tablelist
DO
tname = 'create table '+i.tablename+'_2112 like '+i.tablename+';' ;
tname1 = 'insert into '+i.tablename+'_2112 (select * from '+i.tablename+');';
call DBMS_OUTPUT.PUT(tname)
execute immediate tname;
execute immediate tname;
--set tname=i.tablename
END FOR;
--RETURN tname ;
--COMMIT;
END test;
any help is much appreciated
You need two differnt terminators - one for the statements within the procedure and one for the CREATE PPROCEDURE itself.
Check out this command to set a terminator
--#SET TERMINATOR #
And also chekc out this question & answers

Trying to use temporary table in IBM DB2 and facing issues

I am getting the following error while creating a stored procedure for testing purpose:
SQL Error [42601]: An unexpected token "DECLARE GLOBAL TEMPORARY TABLE
SESSION" was found following "RSOR WITH RETURN FOR". Expected tokens may include: "".. SQLCODE=-104, SQLSTATE=42601, DRIVER=4.21.29
Code:
CREATE OR REPLACE PROCEDURE Test ( IN GE_OutPutType SMALLINT)
----------------------------------------------------------------------------------------------------
DYNAMIC RESULT SETS 1 LANGUAGE SQL
BEGIN
DECLARE C CURSOR WITH RETURN FOR DECLARE GLOBAL TEMPORARY TABLE
SESSION.TEMP (DATE CHAR(10) NOT NULL,
SALARY DECIMAL(9,
2) ,
COMM DECIMAL(9,
2));
INSERT
INTO
SESSION.TEMP (DATE,
SALARY,
COMM) SELECT
VARCHAR_FORMAT(CURRENT_DATE,
'MM/DD/YYYY'),
10.2,
11.5
FROM
sysibm.sysdummy1
IF GE_OutPutType = 1
BEGIN
SELECT
*
FROM
TEMP
ELSEIF GE_OutPutType = 2 SELECT
'HEADER' CONCAT SPACE(1979) CONCAT 'H'
FROM
sysibm.sysdummy1
END OPEN C;
END
Your syntax is not valid.
You must declare your temporary table independently of your cursor.
You cannot combine these in a single statement.
Use dynamic-SQL features to achieve what you need.
Use instead the format:
Declare c1 cursor with return to caller for Statement1
and
set v_cursor_text = 'select ... from session.temp ; `
then use
prepare Statement1 from v_cursor_text;
and before you exit the stored procedure you need to leave the cursor opened:
open c1;
Do study the Db2 online documentation to learn more about these features.
Here is a small fragment of your procedure showing what I mean:
CREATE OR REPLACE PROCEDURE mytest ( IN GE_OutPutType SMALLINT)
DYNAMIC RESULT SETS 1
LANGUAGE SQL
specific mytest
BEGIN
DECLARE v_cursor_text varchar(1024);
DECLARE C1 CURSOR WITH RETURN FOR Statement1;
DECLARE GLOBAL TEMPORARY TABLE SESSION.TEMP (
DATE CHAR(10) NOT NULL,
SALARY DECIMAL(9,
2) ,
COMM DECIMAL(9,
2))
with replace on commit preserve rows not logged;
INSERT INTO SESSION.TEMP (DATE, SALARY, COMM)
SELECT VARCHAR_FORMAT(CURRENT_DATE, 'MM/DD/YYYY'),
10.2,
11.5
FROM sysibm.sysdummy1 ;
if GE_OutPutType = 1
then
set v_cursor_text = 'select * from session.temp';
end if;
if GE_OutPutType = 2
then
set v_cursor_text = 'select ''header'' concat space(1979) concat ''H'' from sysibm.sysdummy1';
end if;
prepare Statement1 from v_cursor_text;
open c1;
END#

Trying to run dynamic sql using a UDF in DB2

I am very new to DB2 even though have experience in Oracle. I am not able to resolve this issue.I have a requirement where I need to find missing child records in the parent table .The parent table , child table and the join_key are all passed as input parameter.
I have tried this in a procedure was able to achieve this, but the admin wants it in a function so that they can just use it in a select statment and get the result in a table format. Since the parent table , child table and the join_key are comming as input parement, I am not able to run them as dynamic sql.
create or replace function missing_child_rec(PARENT_TBL VARCHAR(255),JOIN_KEY VARCHAR(255),CHILD_TBL VARCHAR(255))
RETURNS TABLE(Key VARCHAR(255))
LANGUAGE SQL
BEGIN
DECLARE V_SQL VARCHAR(500);
DECLARE C_SQL CURSOR WITH RETURN FOR S_SQL;
SET V_PARENT_TAB = PARENT_TBL;
SET V_KEY = JOIN_KEY;
SET V_CHILD_TAB = CHILD_TBL;
SET V_SQL = 'SELECT DISTINCT '|| JOIN_KEY || ' FROM ' || V_CHILD_TAB || ' A WHERE NOT EXISTS
(SELECT ' ||V_KEY || ' FROM ' || V_PARENT_TAB || ' B WHERE A.'||JOIN_KEY || '= B.'||JOIN_KEY ||' )' ;
PREPARE S_SQL FROM V_SQL;
OPEN C_SQL;
CLOSE C_SQL;
RETURN
END
When I try to compile it , it says prepare is invalid , I have tried even execute immediate but that also gave error.Can you please help me with how to use dynamic sql in UDF or an alternative logic for this problem
There is more than one way to solve this, here's one way.
If you already have a working stored-procedure that returns the correct result-set then you can call that stored-procedure from a pipelined table function. The idea is that a pipelined table function can consume the result-set and pipe it to the caller.
This will work on Db2-LUW v10.1 or higher, as long as the database is not partitioned over multiple nodes.
It may work on Db2-for-i v7.1 or higher.
It will not work with Db2 for Z/os at current versions.
Suppose your stored procedure is sp_missing_child_rec and it takes the same input parameters as the function you show in your question, and suppose the data type of the join column is varchar(100).
The pipelined wrapper table function would look something like this:
--#SET TERMINATOR #
create or replace function missing_child_rec(PARENT_TBL VARCHAR(255),JOIN_KEY VARCHAR(255),CHILD_TBL VARCHAR(255))
returns table ( join_column_value varchar(100))
begin
declare v_rs result_set_locator varying;
declare v_row varchar(100); -- to match the join_column_datatype, adjust as necessary
declare sqlstate char(5) default '00000';
CALL sp_missing_child_rec( parent_tbl, join_key, child_tbl);
associate result set locator (v_rs) with procedure sp_missing_child_rec ;
allocate v_rscur cursor for result set v_rs;
fetch from v_rscur into v_row;
while ( sqlstate = '00000') do
pipe(v_row);
fetch from v_rscur into v_row;
end while;
return;
end#
select * from table(missing_child_rec( 'parent_table' , 'join_column', 'child_table'))
#

PostgreSQl function return multiple dynamic result sets

I have an old MSSQL procedure that needs to be ported to a PostgreSQL function. Basically the SQL procedure consist in a CURSOR over a select statement. For each cursor entity i have three select statements based on the current cursor output.
FETCH NEXT FROM #cursor INTO #entityId
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT * FROM table1 WHERE col1 = #entityId
SELECT * FROM table2 WHERE col2 = #entityId
SELECT * FROM table3 WHERE col3 = #entityId
END
The tables from the SELECT statements have different columns.
I know that the PostgreSQL use refcursor in order to return multiple result sets but the question is if is possible to open and return multiple dynamic refcursors inside of a loop?
The Npgsql .NET data provider is used for handling the results.
Postgres test code with only 1 cursor inside loop:
CREATE OR REPLACE FUNCTION "TestCursor"(refcursor)
RETURNS SETOF refcursor AS
$BODY$
DECLARE
entity_id integer;
BEGIN
FOR entity_id IN SELECT "FolderID" from "Folder"
LOOP
OPEN $1 FOR SELECT * FROM "FolderInfo" WHERE "FolderID" = entity_id;
RETURN NEXT $1;
CLOSE $1;
END LOOP;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
Then the test code:
BEGIN;
SELECT * FROM "TestCursor"('c');
FETCH ALL IN c;
COMMIT;
The SELECT * FROM "TestCursor"('c'); output is like on screenshot:
Then when i try to fetch data i get the error: ERROR: cursor "c" does not exist
You can emulate it via SETOF refcursor. But it is not good idea. This T-SQL pattern is not supported well in Postgres, and should be prohibited when it is possible. PostgreSQL support functions - function can return scalar, vector or relation. That is all. Usually in 90% is possible to rewrite T-SQL procedures to clean PostgreSQL functions.

How to select from variable that is a table name n Postgre >=9.2

i have a variable that is a name of a table. How can i select or update from this using variable in query , for example:
create or replace function pg_temp.testtst ()
returns varchar(255) as
$$
declare
r record; t_name name;
begin
for r in SELECT tablename FROM pg_tables WHERE schemaname = 'public' limit 100 loop
t_name = r.tablename;
update t_name set id = 10 where id = 15;
end loop;
return seq_name;
end;
$$
language plpgsql;
it shows
ERROR: relation "t_name" does not exist
Correct reply is a comment from Anton Kovalenko
You cannot use variable as table or column name in embedded SQL ever.
UPDATE dynamic_table_name SET ....
PostgreSQL uses a prepared and saved plans for embedded SQL, and references to a target objects (tables) are deep and hard encoded in plans - a some characteristics has significant impact on plans - for one table can be used index, for other not. Query planning is relatively slow, so PostgreSQL doesn't try it transparently (without few exceptions).
You should to use a dynamic SQL - a one purpose is using for similar situations. You generate a new SQL string always and plans are not saved
DO $$
DECLARE r record;
BEGIN
FOR r IN SELECT table_name
FROM information_schema.tables
WHERE table_catalog = 'public'
LOOP
EXECUTE format('UPDATE %I SET id = 10 WHERE id = 15', r.table_name);
END LOOP;
END $$;
Attention: Dynamic SQL is unsafe (there is a SQL injection risks) without parameter sanitization. I used a function "format" for it. Other way is using "quote_ident" function.
EXECUTE 'UPDATE ' || quote_ident(r.table_name) || 'SET ...