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 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#
I'm wondering if I can run the if statement by itself, or it can't stand on its own, has to be in a nested statement. I can't directly run the following code.
IF tax_year=2005 THEN
UPDATE table1 SET column1=column1*3;
ELSIF tax_year=2006 THEN
UPDATE table1 SET column1=column1*5;
ELSIF tax_year=2007 THEN
UPDATE table1 SET column1=column1*7;
END IF;
Also, I didn't write it out that when tax_year=2008, column1=column1. I'm not sure if it needs to be in the code since column1 won't change in 2008.
Thanks for your help!
IF / ELSIF / ELSE is part of PL/pgsql, which is an extension of pg, and it's enabled for new database by default.
You can create a function to wrap the IF statements. And call the function to execute these statements.
e.g
-- create function,
CREATE OR REPLACE FUNCTION fun_dummy_tmp(id_start integer, id_end integer) RETURNS setof dummy AS $$
DECLARE
BEGIN
IF id_start <= 0 THEN
id_start = 1;
END IF;
IF id_end < id_start THEN
id_end = id_start;
END IF;
return query execute 'select * from dummy where id between $1 and $2' using id_start,id_end;
return;
END;
$$ LANGUAGE plpgsql;
-- call function,
select * from fun_dummy_tmp(1, 4);
-- drop function,
DROP FUNCTION IF EXISTS fun_dummy_tmp(integer, integer);
And, there is a CASE statement, which might be a better choice for your requirement.
e.g
-- create function,
CREATE OR REPLACE FUNCTION fun_dummy_tmp(id integer) RETURNS varchar AS $$
DECLARE
msg varchar;
BEGIN
CASE id%2
WHEN 0 THEN
msg := 'even';
WHEN 1 THEN
msg := 'odd';
ELSE
msg := 'impossible';
END CASE;
return msg;
END;
$$ LANGUAGE plpgsql;
-- call function,
select * from fun_dummy_tmp(6);
-- drop function,
DROP FUNCTION IF EXISTS fun_dummy_tmp(integer);
You can refer to postgresql document for control statement for the details.
I did it with the following code:
-- UPDATE houston_real_acct_1single1property
-- SET convert_market_value=
-- CASE
-- WHEN tax_year=2005 THEN total_market_value*1.21320615034169
-- WHEN tax_year=2006 THEN total_market_value*1.17961794019934
-- WHEN tax_year=2007 THEN total_market_value*1.15884093604151
-- WHEN tax_year=2008 THEN total_market_value*1.12145267335906
-- WHEN tax_year=2009 THEN total_market_value*1.11834431349904
-- WHEN tax_year=2010 THEN total_market_value*1.0971664297633
-- WHEN tax_year=2011 THEN total_market_value*1.06256515125065
-- WHEN tax_year=2012 THEN total_market_value*1.04321957955664
-- WHEN tax_year=2013 THEN total_market_value*1.02632796014915
-- WHEN tax_year=2014 THEN total_market_value*0.998472101797389
-- WHEN tax_year=2015 THEN total_market_value
-- END;
I'm trying to determine whether the content of a field is an integer value.
In firebird 2.5 there is "similar to", but this wasn't available in 2.1 yet.
Thank you for your answer.
For now I'll go with:
substring(fieldname from 1 for 1) > '0' and
substring(fieldname from 1 for 1) < '9'
This procedure using error handling, returns field value as is if contents is integer, otherwise return 0
SET TERM ^ ;
create or alter procedure INT_CHECK (
IN_STR varchar(100))
returns (
ORESULT integer)
as
BEGIN
/* because WHEN works for the entire block use a separate BEGIN..END*/
begin -- START OF BLOCK
oresult = cast(:in_str as integer);
when any do
begin
oresult = 0;
end
end -- END OF BLOCK
suspend;
END^
SET TERM ; ^
Here's the function:
CREATE OR REPLACE FUNCTION get_img(ptype text, pid integer, pdpi integer)
RETURNS bytea AS
$BODY$
declare psize char(1); pimg bytea;
begin
select size into psize from dpi_size where dpi in(select max(dpi) from dpi_size where dpi <= pdpi);
select coalesce(psize, 's') into psize;
if ptype = 'cat' then
execute 'select img_' || psize || ' into pimg from cat where id = $1' using pid;
end if;
return pimg;
end; $BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION get_img(text, integer, integer)
OWNER TO postgres;
Table cat has img_s, img_m, img_l and an id. Here's what I get when I run select.
select handyman_get_img ('cat', 11, 320)
ERROR: relation "pimg" already exists CONTEXT: SQL statement "select
img_l into pimg from cat where id = $1" PL/pgSQL function
get_img(text,integer,integer) line 8 at EXECUTE statement
Can anyone enlighten me why it says 'pimg' already exists?
Thanks.
You can't use a variable inside the string literal for execute.
The string passed to execute is run "as is" and select .. into pimg from ... is an old (non-standard) syntax that does the same as create table pimg as select ....
If you want to store the result of an execute into a variable you need to use a different syntax:
execute 'select img_' || psize || ' from cat where id = $1'
into pimg
using pid;
Note how the into is outside of the string that is being executed.