postgresql embedded function return - postgresql

I'm trying to call a function from a trigger function and don't understand what control structure to use. Here's the situation:
I have 3 tables (table1, table2, table3) and two functions (Fct1 and Fct2).
Fct1 is a trigger function triggered after an insert in table1 and which makes insert in table2:
CREATE OR REPLACE FUNCTION Fct1()
RETURNS TRIGGER AS
$BODY$
BEGIN
TRUNCATE "table2";
INSERT INTO "table2"
SELECT ... FROM "table1";
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
The trigger is:
CREATE TRIGGER trig_fct1
AFTER INSERT
ON table1
FOR EACH ROW
WHEN ((pg_trigger_depth() < 1))
EXECUTE PROCEDURE Fct1();
If I do after that a SELECT "Fct2"(); everything works fine, but if I add in Fct1 a PERFORM "Fct2"(); , like this:
CREATE OR REPLACE FUNCTION Fct1()
RETURNS TRIGGER AS
$BODY$
BEGIN
TRUNCATE "table2";
INSERT INTO "table2"
SELECT ... FROM "table1";
TRUNCATE "table3";
PERFORM "Fct2"(); -- will insert into table3
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
It takes much more time to run (I never waited for the end, it's too long).
Fct2 looks like this
CREATE OR REPLACE FUNCTION "Fct2"()
RETURNS void AS
$BODY$
BEGIN
INSERT INTO "table3" ...;
RETURN;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
So, there is something I don't understand and I think it is related to these 'RETURNs' which are not clear to me. I have tried different 'solutions' but I always got errors mentioning some 'return' mismatches. Any suggestions ?
I'm using PostgreSQL 9.6

To capture long running SQL statements from functions in the log, you can use auto_explain with auto_explain.log_nested_statements set to on. But if the query doesn't even finish, that won't help a lot.
My bet is that you are blocked by a database lock. Set log_lock_waits to on and see if something is reported in the log. You should also query pg_locks to see if there are locks requested but not granted.

Related

Duplicate INSERT to another PostgreSQL table

I have checked similar threats but not match exactly that I am trying to do.
I have two identical tables:
t_data: we will have the data of the last two months. There will be a job for delete data older than two months.
t_data_historical: we will have all data.
I want to do: If an INSERT is done into t_data, I want to "clone" the same query to t_data_historical.
I have seen that can be done with triggers but there is something that I don't understand: in the trigger definition I need to know the columns and values and I don't want to care about the INSERT query (I trust of the client that to do the query), I only clone it.
If you know that the history table will have the same definition, and the name is always constructed that way, you can
EXECUTE format('INSERT INTO %I.%I VALUES (($1).*)',
TG_TABLE_SCHEMA,
TG_TABLE_NAME || '_historical')
USING NEW;
in your trigger function.
That will work for all tables, and you don't need to know the actual columns.
Laurenz,
I created the trigger like:
CREATE OR REPLACE FUNCTION copy2historical() RETURNS trigger AS
$BODY$
BEGIN
EXECUTE format('INSERT INTO %I.%I VALUES (($1).*)', TG_TABLE_SCHEMA, TG_TABLE_NAME || '_historical')
USING NEW;
END;
RETURN NULL;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
CREATE TRIGGER copy2historical_tg
AFTER INSERT
ON t_data
FOR EACH ROW
EXECUTE PROCEDURE copy2historical();
thanks for your help.

Create temp table in a STABLE stored procedure in postgresql

I would like to create a temp table in a stored procedure which has a STABLE volatility category setted to store the result of a select for later usage in the stored procedure. At the end of the stored procedure this temp table is deallocated and i am sure that this temp table does not have any affect on the database, because as far as i know with this volatility category i ensure the optimizer that this stored procedure will not affect the database.
So i would like to do something like this:
Create a stored procedure which returns with a query:
CREATE OR REPLACE FUNCTION storedproc()
RETURNS TABLE
(Egy TEXT,
Ketto TEXT)
AS $$
BEGIN
RETURN QUERY SELECT * FROM temptable;
END;
$$ LANGUAGE plpgsql;
Create a stored procedure which is using the previous query:
CREATE OR REPLACE FUNCTION stablefunction()
RETURNS TABLE
(Egy TEXT,
Ketto TEXT)
AS $$
BEGIN
-- I would like to store the results here for later usage
CREATE TEMP TABLE buba AS select * from storedproc();
-- Do other stuff
-- ...
-- Reuse the results here which was stored before
END;
$$ LANGUAGE plpgsql
STABLE;
But when i want to execute this stored procedure as this:
DO
$$
BEGIN
perform stablefunction() ;
END;
$$ LANGUAGE plpgsql;
i get the following error message:
ERROR: CREATE TABLE AS is not allowed in a non-volatile function
Maybe this is not the intended usage of the stored procedures, but then is there a way for store the result of a query inside of the stored procedure for later usage in the same stored procedure, maybe like a handle or somethings?
The documentation states clearly: A stable function cannot modify the database. A temporary table is a part of a database as well, so you cannot create it, insert into, delete from etc. Your concept seems a bit strange but I don't want to judge it. There is a trick that allows what you want to do. Perfom all actions on the temp table using other functions that do not have to be stable. Example:
create or replace function create_my_temp_table()
returns void language plpgsql volatile as $$
begin
create temp table temp_table(id int);
insert into temp_table values (123);
end $$;
create or replace function stable_function()
returns text language plpgsql stable as $$
begin
perform create_my_temp_table();
return 'ok';
end $$;
Test:
select stable_function();
stable_function
-------------
ok
(1 row)
select * from temp_table;
id
-----
123
(1 row)

Why is my dblink trigger not updating?

I have setup a trigger on my table to update inserted rows using a dblink. I used dblink because want the trigger to update the row async.
I have tested the dblink update in pgAdmin SQL query tool succesfully. However, when I insert a row the trigger runs but no rows are updated.
Is there something I'm missing regarding dblink?
CREATE OR REPLACE FUNCTION locates_data.async_update_geom()
RETURNS trigger AS
$BODY$
BEGIN
-- as this is an after trigger, NEW contains all the information we need even for INSERT
perform dblink('dbname=devtable00 host=10.1.1.98 port=5432 user=admin password=*****', 'update locates_data.request set geom = ST_Transform(ST_setSRID(ST_MakePoint('||NEW.longitude||','||NEW.latitude||'), 4326),3857) where request_pk = '||NEW.request_pk||'');
RAISE NOTICE 'UPDATING geo data for %, [%,%]' , NEW.request_pk, NEW.latitude, NEW.longitude;
RETURN NEW; -- result is ignored since this is an AFTER trigger
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION locates_data.async_update_geom()

Calling a function for each updated row in postgresql

I have a sql UPDATE statement in a plpgsql function. I now want to call the pg_notify function for each updated row and am uncertain if my solution is the best possibility.
I am not aware of any position in the UPDATE statement itself where I could apply the function. I don't think it is possible in the SET part and if I would apply the function in the WHERE part, it would be applied to each row as it is checked and not only the updated rows, correct?
I therefore thought I could use the RETURNING part for my purposes and designed the function like this:
CREATE OR REPLACE FUNCTION function_name() RETURNS VOID AS $BODY$
BEGIN
UPDATE table1
SET a = TRUE
FROM table2
WHERE table1.b = table2.c
AND <more conditions>
RETURNING pg_notify('notification_name', table1.pk);
END;
$BODY$ LANGUAGE 'plpgsql' VOLATILE;
Unfortunately this gave me an error saying that I am not using or storing the return value of the query anywhere. I therefore tried putting PERFORM in front of the query but this seemed to be syntactically incorrect.
After trying different combinations with PERFORM my ultimate solution is this:
CREATE OR REPLACE FUNCTION function_name() RETURNS VOID AS $BODY$
DECLARE
dev_null INTEGER;
BEGIN
WITH updated AS (
UPDATE table1
SET a = TRUE
FROM table2
WHERE table1.b = table2.c
AND <more conditions>
RETURNING pg_notify('notification_name', table1.pk)
)
SELECT 1 INTO dev_null;
END;
$BODY$ LANGUAGE 'plpgsql' VOLATILE;
This works as it is supposed to, but I feel like there should be a better solution which does not temporarily store a useless result and does not use a useless variable.
Thank you for your help.
** EDIT 1 **
As can be seen in #pnorton 's answer, a trigger would do the trick in most cases. For me, however, it is not applicable as the receiver of the notifications also sometimes updates the table and I do not want to generate notifications in such a case
"I have a sql UPDATE statement in a plpgsql function. I now want to
call the pg_notify function for each updated row "
Ok I might be tempted to use a trigger Eg
CREATE TABLE foobar (id serial primary key, name varchar);
CREATE OR REPLACE FUNCTION notify_trigger() RETURNS trigger AS $$
DECLARE
BEGIN
PERFORM pg_notify('watch_tb_update', TG_TABLE_NAME || ',id,' || NEW.id );
RETURN new;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER foobar_trigger AFTER INSERT ON foobar
FOR EACH ROW EXECUTE PROCEDURE notify_trigger();
LISTEN watch_tb_update;
INSERT into foobar(id, name) values(1,'test_name');
I've tested this and it works fine

Postgres trigger that will insert a row into another table before it's deleted

I have a table (entry_table) filled with various geographic location data currently used by clients for our front end Web feature service. If a user deleted their entry in the WFS it is deleted in our postgres database. I would like to create a trigger that will run an INSERT command to copy the row(roughly 25 columns of data) to a second table(historical_entry_table) so if the entries are needed again in the future they can be easily retrieved.
Here's what I have working so far. I'm new to Triggers so I know the syntax is off. Not sure where to go from here. Im running postgres 8.4
In the table :
CREATE TRIGGER trigger_name BEFORE DELETE
ON entry_table
FOR EACH ROW
EXECUTE PROCEDURE trigger_backup_row
The Function itself:
CREATE OR REPLACE FUNCTION trigger_backup_row()
LANGUAGE SQL
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO historical_entry_table (col1, col2, etc) values (OLD.col1, OLD.col2, OL
RETURN NULL:
END;
$BODY$
I will put a working example here:
CREATE OR REPLACE FUNCTION trigger_backup_row()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO PCIcards_backup (MODEL, SUBSYSTEM_DEVICE, ADAPTER, MAPPING) values (NEW.MODEL, NEW.SUBSYSTEM_DEVICE, NEW.ADAPTER, NEW.MAPPING);
RETURN NEW;
END;
$BODY$
language PLPGSQL
As you can see, you only need to set the language at the end of a file with param PLPGSQL because SQL cannot return triggers.
you have just about got it. your code here, updated :
CREATE OR REPLACE FUNCTION trigger_backup_row()
RETURN trigger AS
$BODY$
BEGIN
INSERT INTO historical_entry_table (col1, col2, etc) values (OLD.col1, OLD.col2, OLD.etc);
END;
$BODY$
Posting the correct version of the last answer:
CREATE OR REPLACE FUNCTION trigger_backup_row()
RETURN trigger
LANGUAGE plpgsql
AS
$$
BEGIN
INSERT INTO historical_entry_table (col1, col2, etc) values (OLD.col1, OLD.col2, OLD.etc);
RETURN new;
END;
$$