Using PostgreSQL 13.2, wherein a stored procedure (the Requestor) is given a name of a list of stored procedures to run (the job group). All sp's executed this way are coded to write a log record as their last task. I have chosen to pull that 'append log' code from all of the sp's, and instead send back the log record (always a single record) using an INOUT rowtype param, but have run into trouble. In my example below, the requestor sp will load the records returned from the sp's it calls into a temp table shaped like the permanent log table.
That permanent table looks like this:
create table public.job_log (
log_id integer,
event_id integer,
job_id integer,
rows_affected integer);
Any one of the jobs that is executed by the requestor sp might look like this one:
CREATE OR REPLACE procedure public.get_log_rcd(
inout p_log_rcd public.job_log)
LANGUAGE 'plpgsql'
as
$BODY$
declare
v_log_id integer = 40;
v_event_id integer = 698;
v_job_id integer = 45;
v_rows_affected integer = 60;
begin
select
v_log_id
, v_event_id
, v_job_id
, v_rows_affected
into
p_log_rcd.log_id,
p_log_rcd.event_id,
p_log_rcd.job_id,
p_log_rcd.rows_affected;
end;
$BODY$
This sample sp doesn't do anything--it's purpose here is only to simulate initialize of the log parameters to return to caller.
Again, the requestor sp that's going to run jobs like the one above creates a temp table with the same structure as the permanent log:
drop table if exists tmp_log_cache;
create temp table tmp_log_cache as table public.job_log with no data;
If the requestor sp didn't have to do dynamic SQL, it would look something like this block here:
do
$$
declare
big_local public.job_log;
begin
call public.get_log_rcd( big_local );
insert into tmp_log_cache (
log_id
, event_id
, job_id
, rows_affected )
values (
big_local.log_id
, big_local.event_id
, big_local.job_id
, big_local.rows_affected);
end;
$$;
Doing a
select * from tmp_log_cache;
Returns a row containing the 4 column values expected, all is well. But, dynamic execution is required. And, as I'm sure most folks here know, the following dog don't hunt:
do
$$
declare
big_local public.job_log;
v_query_text varchar;
v_job_name varchar = 'public.get_log_rcd';
begin
select 'call ' || v_job_name || '( $1 );'
into v_query_text;
execute v_query_text using big_local::public.job_log;
insert into tmp_log_cache (
log_id
, event_id
, job_id
, rows_affected )
values (
big_local.log_id
, big_local.event_id
, big_local.job_id
, big_local.rows_affected);
end;
$$;
The above dynamic statement executes without error, but the insert statement only has NULL values to work with--a row is inserted, all nulls. Any suggestions warmly welcomed. The sp's that comprise the various job groups could probably have been implemented as functions, although in all cases their primary tasks are to massage, normalize, cleanse telemetry data, not to spit anything out, per se.
Hmm, the documentation states that "parameter symbols (...) only work in SELECT, INSERT, UPDATE, and DELETE commands.", so this probably isn't possible using parameters.
But as a workaround you can build a dynamic DO and include a variable to get the values and the INSERT in there.
DO
$o$
DECLARE
v_query_text varchar;
v_job_name varchar := format('%I.%I',
'public',
'get_log_rcd');
BEGIN
v_query_text := concat('DO ',
'$i$ ',
'DECLARE ',
' big_local public.job_log; ',
'BEGIN ',
' CALL ', v_job_name, '(big_local); ',
' INSERT INTO tmp_log_cache ',
' (log_id, ',
' event_id, ',
' job_id, ',
' rows_affected) ',
' VALUES (big_local.log_id, ',
' big_local.event_id, ',
' big_local.job_id, '
' big_local.rows_affected); ',
'END; ',
'$i$; ');
EXECUTE v_query_text;
END;
$o$;
db<>fiddle
Thanks--I would not have considered the ability to execute a 'do' using execute. It just would not have occurred to me. Well, here's my solution: flip to functions.
With the understanding that my 'Requestor' is only given sp's to run because that's what we had to do with SQL Server and it was reflex, I did the 1-line change needed to flip my example sp above to a function:
CREATE OR REPLACE function public.get_log_rcdf(
inout p_log_rcd public.job_log)
returns public.job_log
LANGUAGE 'plpgsql'
as
$BODY$
declare
v_log_id integer = 40;
v_event_id integer = 698;
v_job_id integer = 45;
v_rows_affected integer = 60;
begin
select
v_log_id
, v_event_id
, v_job_id
, v_rows_affected
into
p_log_rcd.log_id,
p_log_rcd.event_id,
p_log_rcd.job_id,
p_log_rcd.rows_affected;
end;
$BODY$
In fact, the change to a function required the addition of a RETURNS line. Done. Then, the dynamic call was tweaked to a SELECT and the execute modified with an INTO:
do
$$
declare
big_local public.job_log;
v_query_text varchar;
v_job_name varchar = 'public.get_log_rcdf';
begin
select 'select * from ' || v_job_name || '( $1 );'
into v_query_text;
raise info 'SQL text is: %', v_query_text;
execute v_query_text into big_local using big_local;
insert into tmp_log_cache (
log_id
, event_id
, job_id
, rows_affected )
values (
big_local.log_id
, big_local.event_id
, big_local.job_id
, big_local.rows_affected);
end;
$$;
and the process now works exactly as desired. I tidy up my handling of the dynamic function name as illustrated in the first answer, and I think we're done here.
I am trying to create the query as a string and execute that in PostgreSQL 10.
As far as I know, I can use the EXECUTE command to execute my query from a defined string.
Unfortunately, I have got an error: SQL Error [42601]: ERROR: syntax error at or near "execute"
Below is my code:
drop table if exists delinquent;
create table delinquent
(
report_date date
,account_id text
)
;
INSERT INTO delinquent VALUES('2019-07-23', 'a1234');
INSERT INTO delinquent VALUES('2019-07-23', 'b5679');
--------------
drop table if exists output1;
create temp table output1
(
report_date date
,account_id text
)
;
--------------
do $$
declare table_name text := 'delinquent';
begin
truncate table output1;
insert into output1
execute concat('select * from ',table_name);
end; $$;
select * from output1;
Anybody has an idea on what is wrong and what to do about it?
Many thanks,
You need to run the complete INSERT statement as dynamic SQL. And to build dynamic SQL, using format() is highly recommended to properly deal with identifiers and literals:
do $$
declare
table_name text := 'delinquent';
some_value text := 'a1234';
begin
truncate table output1;
execute format('insert into output1 select * from %I where some_column = %L',
table_name, some_value);
end; $$;
select *
from output1;
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
#
I have the following function:
In which I am updating one database table by joining other database table by using the dblink().
I have installed:
create extension dblink;
The more details as shown below:
CREATE OR REPLACE FUNCTION Fun_test
(
Table_Name varchar
)
RETURNS void AS
$BODY$
DECLARE
dynamic_statement varchar;
BEGIN
perform dblink_connect('port=5234 dbname=testdb user=postgres password=****');
dynamic_statement := 'With CTE AS
(
Select HNumber,JoiningDate,Name,Address
From '|| Table_Name ||'c
)
, Test_A
AS
(
Select Row_Number() over ( Partition by PNumber order by Date1 Desc,Date2 Desc) AS roNum,
Name,PNumber,Date1,Address
From dblink(
''Select distinct PNumber,
(
case when fname is null then '' else fname end || '' ||
case when lname is null then '' else lname end
) as FullName,
Address,
Date1,Date2
From testdb_Table
inner join CTE on CTE.HNumber = PNumber''
) Num
)
Update CTE
Set
Name = Test_A.FullName
,SubAddress_A = Test_A.Address
,Date1 = Test_A.Date1
from CTE
left outer join Test_A on
CTE.HNumber= Test_A.PNumber
where roNum =1';
RAISE INFO '%',dynamic_statement;
EXECUTE dynamic_statement;
perform dblink_disconnect();
END;
$BODY$
LANGUAGE PLPGSQL;
Calling Function:
select fun_test('test1');
Getting an error:
ERROR: a column definition list is required for functions returning "record"
LINE 11: From dblink
^
You have to tell PostgreSQL what the columns the dblink query will return are.
See the manual for dblink for details.
This is the same as for any function returning a runtime-determined record type. You can't query it without telling PostgreSQL what the column layout of the results will be.
You use a column specifier list, e.g.
SELECT * FROM my_function_returning_record() f(col1 text, col2 integer);
If you are on a current PostgreSQL version you may want to look at postgres_fdw as an alternative to dblink.
I have not been able to get this code block to run my query against postgres in CFQuery of ColdFusion:
<cfquery name="uiCustomColumn" datasource="#arguments.dsn#">
DECLARE resultValue int;
DECLARE nextId bigint;
BEGIN
IF (( select count( udc_id ) from user_defined_column WHERE udc_is_active = true ) >= 10) THEN
INSERT INTO user_defined_column(udc_id)
VALUES(<cfqueryparam value="#this.getLabel()#" cfsqltype="cf_sql_varchar" maxlength="25">)
END IF;
END;
</cfquery>
What you have there is plpgsql syntax (the default PostgreSQL procedural language), not SQL.
You'd need to wrap this in a DO command or CREATE FUNCTION with it.
Or rewrite it with SQL syntax. Something along these lines:
INSERT INTO user_defined_column(udc_id)
SELECT <this.getLabel()>
WHERE (
SELECT count(udc_id) > 9
FROM user_defined_column
WHERE udc_is_active
)