postgresql execute dynamic sql command - postgresql

I'm slowly learning more about PostgreSQL, as we are attempting to move to it from MSSQL Server.
In MSSQL I have the following code:
DECLARE ServiceabilityParameters
CURSOR FORWARD_ONLY READ_ONLY STATIC LOCAL FOR
SELECT WorkbookParameterType.ID,
WorkbookParameterType.Name,
WorkbookParameter.DefaultValue,
WorkbookParameter.CommandText
FROM WorkbookParameter
JOIN WorkbookParameterType ON WorkbookParameterType.ID = WorkbookParameter.WorkbookParameterTypeID
JOIN WorkbookParameterDirectionType ON WorkbookParameterDirectionType.ID = WorkbookParameter.WorkbookParameterDirectionTypeID
AND WorkbookParameterDirectionType.Writable = 1
WHERE WorkbookParameter.WorkbookID = #WorkbookID
OPEN ServiceabilityParameters
FETCH NEXT FROM ServiceabilityParameters INTO #WorkbookParameterTypeID, #WorkbookParameterTypeName, #WorkbookDefaultValue, #WorkbookCommandText
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #ActualValue NVARCHAR(256) = NULL
IF #WorkbookCommandText IS NOT NULL
BEGIN
EXEC sp_executesql #statement = #WorkbookCommandText,
#params = N'#ApplicationContainerID INT, #Value NVARCHAR(256) OUTPUT',
#ApplicationContainerID = #ApplicationContainerID,
#Value = #ActualValue OUTPUT
END
IF #ActualValue IS NULL AND #WorkbookDefaultValue IS NOT NULL
BEGIN
SET #ActualValue = #WorkbookDefaultValue
END
INSERT #InputParameters (
ID, Name, Value
) VALUES (
#WorkbookParameterTypeID, #WorkbookParameterTypeName, #ActualValue
)
FETCH NEXT FROM ServiceabilityParameters INTO #WorkbookParameterTypeID, #WorkbookParameterTypeName, #WorkbookDefaultValue, #WorkbookCommandText
END
CLOSE ServiceabilityParameters
DEALLOCATE ServiceabilityParameters
I'm trying to work out how to do the sp_executesql part in a PostgreSQL function. I believe that I can do the rest, but most of the examples that I have found show a simple select with maybe a few variables, whereas I need to execute another function, with parameters, where the function name is text in a table.
Many Thanks.

In case you want to execute a function with parameters
EXECUTE 'SELECT Value FROM ' || v_workbookCommandText || '(ApplicationContainerID :=$1)'
INTO v_actualValue
USING v_applicationContainerID;
In case you need select records a function, you can using INOUT refcursor variable
EXECUTE 'SELECT Value FROM ' || v_workbookCommandText || '(ApplicationContainerID :=$1, refcur:= $2)'
INTO v_actualValue
USING v_applicationContainerID, my_cursor;

I think what you want to do is EXECUTE 'some string', like this:
EXECUTE 'SELECT count(*) FROM mytable WHERE inserted_by = $1 AND inserted <= $2'
INTO c
USING checked_user, checked_date;
Another option is to create and use your own PL/PGSQL functions.

Related

How to concate join condition based on input flag value inside cursor in DB2

I have a below SP which has sql query which need to refactor db2 query,in db2 i dont know how to concatenate the flag condition remaining query to main query.
CREATE PROCEDURE EMPLOYEE
(IN EMPID varchar(1000),
IN BFLAG char(3))
RESULT SETS 1
LANGUAGE SQL
P1: BEGIN
SET v_sql = 'select c.id,c.name from emp c'
IF BFLAG <> 'T' THEN
SET v_sql = v_sql ||
' left outer join dept U
where c.empid in (' || EMPID || ') ';
ELSE
SET v_sql = v_sql ||
' where c.empid in (' || EMPID || ') ';
END IF;
how to concatenate query in db2 based on flag value specified above condition.
DECLARE c_id CURSOR WITH RETURN FOR
select c.id,c.name from emp c;
Too many errors.
You should study how the compound-statement must be constructed.
Every variable must be declared.
The order of statements inside is significant: variable declarations first, then statements, cursors. SQL-procedure-statements are afterwards only.
You get erroneous dynamic statement if BFLAG <> 'T' is true: left join without conditions.
If the questions is how to use cursor with dynamic statements, then here is an example:
CREATE PROCEDURE TEST_DYNAMIC (IN P_REST varchar(1000))
DYNAMIC RESULT SETS 1
LANGUAGE SQL
P1: BEGIN
DECLARE v_sql VARCHAR (1000);
DECLARE v_s STATEMENT;
DECLARE v_c1 CURSOR WITH RETURN FOR v_s;
SET v_sql = 'select ' || P_REST;
PREPARE v_s FROM v_sql;
OPEN v_c1;
END
#
CALL TEST_DYNAMIC (' * FROM EMPLOYEE WHERE EMPNO IN (''000010'', ''000020'')')#
If not, then try to compile your routine without errors at least and show the runtime error / unexpected result if any...

Cannot Get Dynamic Exec of SP to Return INOUT Param

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.

Dynamic SQL with nvarchar(max) variable (again)

I have read about a dozen articles here and I am still stumped with this issue.
I am building a dynamic select statement that will update a view on a monthly schedule.
set ansi_nulls on
go
set quoted_identifier on
go
alter procedure [dbo].[Proc_Name_SP]
as
begin
set nocount on
set quoted_identifier off
declare #dbname varchar(10), #schema_id int, #schema_name varchar(10),
#jacro varchar(10), #rec_cnt int, #tot_rec int
declare #SQL_Main nvarchar(max), #SQL_Final nvarchar(max),
#SQL_schema nvarchar(2000), #SQL_Union nvarchar(max)
declare iteration cursor global static for
-- Begin statement for cursor array
select distinct db, code
from linkedserver.db.schema.Directory
where current_stage = 'live'
order by db
-- End statement for cursor array
-- get total number of cursor iterations to know when to stop
-- "union" statements
select #tot_rec = count(*) from (select distinct db, code
from [linkedserver].db.schema.Directory
where current_stage = 'live') as cur
-- begin loop
open iteration
fetch first from iteration into #dbname, #jacro
while ##fetch_status=0
begin
-- the schema used is not consistent. Because of the linked server it was
-- necessary to get the Schema_ID from the sys.tables and then pull the
-- schema name from sys.schema
set #SQL_schema = 'select #sch_id = schema_id from [linkedserver].'+#dbname+'.sys.tables where name = ''Manuscript'''
execute sp_executesql #SQL_schema, N'#sch_id int OUTPUT', #sch_id = #schema_id output
--print #schema_id
set #SQL_schema ='select #sch_name = name from [linkedserver].'+#dbname+'.sys.schemas where schema_id = '+cast(#schema_id as varchar)+''
execute sp_executesql #SQL_schema, N'#sch_name nvarchar(10) OUTPUT', #sch_name = #schema_name output
--print #schema_name
--building Select statement
set #SQL_Main ='
select jcode.Code as BILLING_ACRO
,s.start_dt as BILLING_DATE
,cmpt_ms_nm as MANUSCRIPT
,isnull(jcode.billing_type, ''reviewed'') as Billing_type
from [linkedserver].'+#dbname+'.'+#schema_name+'.Manuscript as m
join [linkedserver].'+#dbname+'.'+#schema_name+'.Step as s on m.ms_id = s.ms_id and m.ms_rev_no = s.ms_rev_no
join (select j_id, Code, billing_type from [linkedserver].db.schema.Directory where db = '''+#dbname+''') as jcode on jcode.j_id = m.j_id
where jcode.Code = '''+#jacro+'''
and m.ms_rev_no = 0
and s.stage_id = 190
and isnull(cmpt_ms_nm, '''') <> ''''
and s.step_id = (select min(s2.step_id)
from [linkedserver].'+#dbname+'.'+#schema_name+'.Step as s2
where s2.stage_id = 190
and s2.ms_id = m.ms_id
and s2.ms_rev_no = m.ms_rev_no)
'
set #rec_cnt = isnull(#rec_cnt, 0) + 1
if #SQL_Union is null
begin
set #SQL_Union = #SQL_Main
end
else if #tot_rec <> #rec_cnt
begin
set #SQL_Union = #SQL_Union + ' union ' + #SQL_Main
end
else
begin
set #SQL_Union = #SQL_Union + #SQL_Main
end
--print #rec_cnt
fetch next from iteration into #dbname, #jacro --next database
end -- while ##FETCH_STATUS=0
close iteration
deallocate iteration
-- build new view
print len(#SQL_Union)
set #SQL_Final = '
ALTER VIEW [dbo].[View_Name_VW]
AS
'+#SQL_Union+'
'
execute sp_executesql #SQL_Final
--grab string variables to table for troubleshooting
insert into Output_SQL(SQL_Final, SQL_Final_Len, SQL_Union, SQL_Union_Len)
select #SQL_Final, LEN(#SQL_Final), #SQL_Union, LEN(#SQL_Union)
set nocount off
end
go
I have read that others have had problems with this type of truncation and I have tried multiple suggestions but in the end the I am getting capped at 68274 in this code with nvarchar(max). For troubleshooting, I am saving the results of the variables and the len of these variables to a table to eliminate the SSMS cap on the display of strings.
I have tried cast(#varible as nvarchar(max)) on the right side of the = sign. I have changed the data type lengths (as the select that is being built is not that large, it is just large after it has been union for each unique customer)
I am open to any suggestions as I have tried many variations of datatype declarations for these variables.

Firebird Stored Procedure defining tables at runtime and returning values

I'm trying to execute some SQL in a stored procedure, setting the table at runtime, and return the selected value:
create or alter procedure TEST3
returns (
THE_VAR varchar(128))
as
declare variable TABLE_NAME varchar(64);
declare variable STMT varchar(128);
begin
/* Procedure Text */
Table_Name = 'users';
stmt = 'select firstname from ' || :table_name || ' where userid = 2 into :the_var';
execute statement stmt;
suspend;
end
It results in Token unknown ...... into.
Can anyone tell me what's wrong with it?
You can't use dynamic SQL to assign values to variables in the stored procedure context in this way. That needs to be handled from the EXECUTE STATEMENT. You need to change your code to something like:
stmt = 'select firstname from ' || :table_name || ' where userid = 2';
execute statement stmt into :the_var;
See also the EXECUTE STATEMENT documentation.

Differences beween 'set' and 'select into' in IBM DB2 SQL PL

When developing in SQL PL, what is the difference between 'set' and 'select into'?
set var = (select count(1) from emp);
select count(1) into var from emp;
Are they completely equivalent? where can I find documention about them?
When issuing a select, and it does not return any value:
select into throws an exception
set gets a null value
You can check the difference with these two stored procedures:
Using set:
create or replace procedure test1 (
in name varchar(128)
)
begin
declare val varchar(128);
set val = (select schemaname
from syscat.schemata where schemaname = name);
end #
Using select into
create or replace procedure test2 (
in name varchar(128)
)
begin
declare val varchar(128);
select schemaname into val
from syscat.schemata where schemaname = name;
end #
Call set
$ db2 "call test1('nada')"
Return Status = 0
Call select into
$ db2 "call test2('nada')"
Return Status = 0
SQL0100W No row was found for FETCH, UPDATE or DELETE; or the result of a
query is an empty table. SQLSTATE=02000
This is a difference between both of them. When using select into, you have to deal with handlers.
They are, to the best of my knowledge
In some cases, you would do one technique over the other ..
eg. You cannot use WITH UR in SET
SET var1=(selct....from t with ur)
but can do
select a into var1 from t with ur
When the result of the query is part of a test condition.
For example, when detaching paritions and waiting for the asynchronous process, the following works:
WHILE (STATUS_PART <> '') DO
CALL DBMS_LOCK.SLEEP(1);
SET STATUS_PART = (SELECT STATUS
FROM SYSCAT.DATAPARTITIONS
WHERE TABSCHEMA = TABLE_SCHEMA
AND TABNAME = TABLE_NAME
AND DATAPARTITIONNAME LIKE 'SQL%' WITH UR);
END WHILE;
But the following does not:
WHILE (STATUS_PART <> '') DO
CALL DBMS_LOCK.SLEEP(1);
SELECT STATUS INTO STATUS_PART
FROM SYSCAT.DATAPARTITIONS
WHERE TABSCHEMA = TABLE_SCHEMA
AND TABNAME = TABLE_NAME
AND DATAPARTITIONNAME LIKE 'SQL%' WITH UR;
END WHILE;
The SELECT INTO works for SELECT statements.
With SET you can directly assign the outcome of a function, do calculations or assign a different variable. e.g.
SET var = var + 1;
SET var1 = var;