Execute dynamic prepared statement inside a trigger - postgresql

I want to use my trigger for logging purposes - every time, when a user inserts something or makes updates, there should appear a new row in my history table. I already have a trigger, which works good. Now it runs every time, when I do inserts. This is how it looks like:
CREATE OR REPLACE FUNCTION public.trigger_history_insert()
RETURNS trigger AS
$BODY$
DECLARE
...
BEGIN
... it does a lot of things here and works absolutely correctly
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION public.trigger_history_insert()
OWNER TO postgres;
Now I want to add logging into this trigger. I tried to follow these two official tutorials ([1], [2]), but ended in failure. This was my first attempt:
DECLARE
...
BEGIN
... everything remains unchanged except this part
EXEC SQL BEGIN DECLARE SECTION;
const char *stmt = 'INSERT INTO history (field1) VALUES (?)';
EXEC SQL END DECLARE SECTION;
EXEC SQL PREPARE mystmt FROM :stmt;
EXEC SQL EXECUTE mystmt USING new.field1;
END;
In this case I get syntax error pointing to this line of code:
EXEC SQL BEGIN DECLARE SECTION;
^
This was my second attempt:
BEGIN
... everything remains unchanged except this part
BEGIN DECLARE SECTION;
const char *stmt = 'INSERT INTO history (field1) VALUES (?)';
END DECLARE SECTION;
PREPARE mystmt FROM :stmt;
EXECUTE mystmt USING new.field1;
END;
Now, I get an error message pointing to this line:
BEGIN DECLARE SECTION;
^
This was my third attempt:
$BODY$
DECLARE
...
const char *stmt = 'INSERT INTO history (field1) VALUES (?)'
BEGIN
...
PREPARE mystmt FROM :stmt;
EXECUTE mystmt USING new.field1;
END
Now I get an error message pointing to this line:
const char *stmt = 'INSERT INTO history ...
^
This was my last attempt:
$BODY$
DECLARE
...
stmt text := 'INSERT INTO history (field1) VALUES (?)'
BEGIN
...
PREPARE mystmt FROM :stmt;
EXECUTE mystmt USING new.field1;
END
And in this final case, the error message points to this line:
PREPARE mystmt FROM :stmt;
^
So, what am I doing wrong and how can I fix it?

You are mixing up PL/pgSQL, a language for writing functions in the database, and ecpg, which is used for embedding database access in C client code.
The correct solution would look like this:
INSERT INTO history (field1) VALUES (NEW.field1);

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

Declaring the table name in constant in Postgres Stored procedure

I have a sample stored procedure where in I have to use a table for multiple operations. I want to declare the table name as a constant and then re-use it wherever required. Below is the sample code which i wrote:
CREATE OR REPLACE FUNCTION get_data()
RETURNS void AS
$func$
DECLARE
table_name_a CONSTANT TEXT = asp.monitoring_bookmark_original;
cursor_file CURSOR FOR
select distinct filename,systemuid from table_name_a;
cursor_data CURSOR FOR
select * from table_name_a where filename = v_filename and systemuid=v_systemuid order by mindatetime, maxdatetime;
BEGIN
--open the file cursor
//logic goes here
END;
$func$
LANGUAGE plpgsql;
When I try to run this procedure I am getting error:
ERROR: missing FROM-clause entry for table "asp"
LINE 1: SELECT asp.monitoring_bookmark_original
What is wrong in this code? How do I correct this?
Well you can use dynamic SQL, but realize dynamic SQL often adds way more complexity. Good when really needed but should be avoided when possible. The following shows what would be needed for what you want to do. Is not having to type the table name for each SQL statement worth the additional trouble?
create or replace function get_data()
returns void as
$func$
declare
table_name_a constant text = 'asp.monitoring_bookmark_original';
file_cursor text = 'select distinct filename,systemuid from %i';
file_ref refcursor;
file_rec record;
data_cursor text =$stmt$select * from %i where filename = '%s' and systemuid= '%s' order by mindatetime, maxdatetime$stmt$;
data_ref refcursor;
data_rec record;
begin
--open the file cursor
open file_ref for execute format(file_cursor,table_name_a);
loop
fetch next from file_ref into file_rec;
exit when not found;
-- and extending from what the second query inplies
open data_ref for execute format(data_cursor,table_name_a,file_rec.filename,file_rec.systemid);
loop
fetch next from data_ref into data_rec;
exit when not found;
--//logic goes here
end loop;
end loop ;
end;
$func$
language plpgsql;

using $$ in execute statement of Postgres

I have a function block like
DO $$
DECLARE tran_year RECORD;
BEGIN
FOR tran_year IN SELECT * FROM tbale1 loop
EXECUTE 'ALTER TABLE mytable ADD CONSTRAINT unique_cons$$aaf1c86 UNIQUE (samplecol)';
END LOOP;
END $$;
in Postgres, when trying to execute this block, I face an issue saying that ERROR: syntax error at or near "aaf1c86".
I also have the need to use $$ in constraint name. Any idea of overcoming this error
As documented in the manual you can use any character sequence between the $ signs if you use the same sequence to end the string literal.
So just use something different the $$ on the outside:
DO $doblock$
DECLARE tran_year RECORD;
BEGIN
FOR tran_year IN SELECT * FROM tbale1 loop
EXECUTE 'ALTER TABLE mytable ADD CONSTRAINT unique_cons$$aaf1c86 UNIQUE (samplecol)';
END LOOP;
END $doblock$;

Syntax error at or near "FOR" while using "execute format" in a function

This function compiled successfully:
CREATE OR REPLACE FUNCTION FieldValidations1(tbl_name varchar(35),col_name varchar(25), error_flag varchar(3))
RETURNS void AS $$
declare
cust_rec RECORD;
BEGIN
execute format($sel$
FOR cust_rec IN SELECT %I FROM %s
LOOP
RAISE NOTICE 'inside loop';
END LOOP;
$sel$,
col_name,tbl_name);
END;
$$ LANGUAGE plpgsql;
But while calling the function,
select FieldValidations1('raw_tbl1','col1','gg');
the error appears like this
ERROR: syntax error at or near "FOR"
LINE 3: FOR cust_rec IN SELECT col1 FROM raw_tbl1
^
QUERY:
FOR cust_rec IN SELECT col1 FROM raw_tbl1
LOOP
RAISE NOTICE 'inside loop';
END LOOP;
CONTEXT: PL/pgSQL function "fieldvalidations1" line 6 at EXECUTE statement
Can anyone explain what's wrong?
You must be aware that plpgsql functions are only tested on a superficial syntactical level in CREATE FUNCTION. It does not find all possible errors, it does not try to evaluate functions.
Would work like this:
CREATE OR REPLACE FUNCTION field_validations2(tbl_name text
, col_name text, error_flag text)
RETURNS void AS
$func$
DECLARE
cust_rec RECORD;
BEGIN
FOR cust_rec IN
EXECUTE format('SELECT %I FROM %I', col_name, tbl_name)
LOOP
RAISE NOTICE 'inside loop';
END LOOP;
END
$func$ LANGUAGE plpgsql;
The proper syntax for a FOR-IN-EXECUTE statement can be found in the manual.
Also fixed a couple of other things. text instead of varchar(n) is just a friendly advice.
It is not possible to execute plpgsql. It must be plain SQL.

Update record of a cursor where the table name is a parameter

I am adjusting some PL/pgSQL code so my refcursor can take the table name as parameter. Therefore I changed the following line:
declare
pointCurs CURSOR FOR SELECT * from tableName for update;
with this one:
OPEN pointCurs FOR execute 'SELECT * FROM ' || quote_ident(tableName) for update;
I adjusted the loop, and voilĂ , the loop went through. Now at some point in the loop I needed to update the record (pointed by the cursor) and I got stuck. How should I properly adjust the following line of code?
UPDATE tableName set tp_id = pos where current of pointCurs;
I fixed the quotes for the tableName and pos and added the EXECUTE clause at the beginning, but I get the error on the where current of pointCurs.
Questions:
How can I update the record?
The function was working properly for tables from the public schema and failed for tables from other schemas (e.g., trace.myname).
Any comments are highly appreciated..
Answer for (i)
1. Explicit (unbound) cursor
EXECUTE is not a "clause", but a PL/pgSQL command to execute SQL strings. Cursors are not visible inside the command. You need to pass values to it.
Hence, you cannot use the special syntax WHERE CURRENT OFcursor. I use the system column ctid instead to determine the row without knowing the name of a unique column. Note that ctid is only guaranteed to be stable within the same transaction.
CREATE OR REPLACE FUNCTION f_curs1(_tbl text)
RETURNS void AS
$func$
DECLARE
_curs refcursor;
rec record;
BEGIN
OPEN _curs FOR EXECUTE 'SELECT * FROM ' || quote_ident(_tbl) FOR UPDATE;
LOOP
FETCH NEXT FROM _curs INTO rec;
EXIT WHEN rec IS NULL;
RAISE NOTICE '%', rec.tbl_id;
EXECUTE format('UPDATE %I SET tbl_id = tbl_id + 10 WHERE ctid = $1', _tbl)
USING rec.ctid;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Why format() with %I?
There is also a variant of the FOR statement to loop through cursors, but it only works for bound cursors. We have to use an unbound cursor here.
2. Implicit cursor in FOR loop
There is normally no need for explicit cursors in plpgsql. Use the implicit cursor of a FOR loop instead:
CREATE OR REPLACE FUNCTION f_curs2(_tbl text)
RETURNS void AS
$func$
DECLARE
_ctid tid;
BEGIN
FOR _ctid IN EXECUTE 'SELECT ctid FROM ' || quote_ident(_tbl) FOR UPDATE
LOOP
EXECUTE format('UPDATE %I SET tbl_id = tbl_id + 100 WHERE ctid = $1', _tbl)
USING _ctid;
END LOOP;
END
$func$ LANGUAGE plpgsql;
3. Set based approach
Or better, yet (if possible!): Rethink your problem in terms of set-based operations and execute a single (dynamic) SQL command:
-- Set-base dynamic SQL
CREATE OR REPLACE FUNCTION f_nocurs(_tbl text)
RETURNS void AS
$func$
BEGIN
EXECUTE format('UPDATE %I SET tbl_id = tbl_id + 1000', _tbl);
-- add WHERE clause as needed
END
$func$ LANGUAGE plpgsql;
SQL Fiddle demonstrating all 3 variants.
Answer for (ii)
A schema-qualified table name like trace.myname actually consists of two identifiers. You have to
either pass and escape them separately,
or go with the more elegant approach of using a regclass type:
CREATE OR REPLACE FUNCTION f_nocurs(_tbl regclass)
RETURNS void AS
$func$
BEGIN
EXECUTE format('UPDATE %s SET tbl_id = tbl_id + 1000', _tbl);
END
$func$ LANGUAGE plpgsql;
I switched from %I to %s, because the regclass parameter is automatically properly escaped when (automatically) converted to text.
More details in this related answer:
Table name as a PostgreSQL function parameter