Sequencing results in function PostgreSQL 9.5 - postgresql

I am setting a function for a trigger triggered when I insert in tableA, which I would first to create an entry in tableB, and then create some other entries in tableC in which there is a foreign key related to tableB. But I get an error since it tries insert into tableC a value in the foreign key field which doesn't exists in the tableB as long as the function didn't finish to run.
Is there a way that inside a function, put some sort of return inside my function but which would not exit the function and then execute the rest of it ? Something which would look like this :
CREATE OR REPLACE FUNCTION trigger1() RETURNS trigger AS
$BODY$
begin
insert into tableB values (new.value);
RETURN NEW;
insert into tableC (id, fkey) values (new.something, new.value);
RETURN NEW;
end;
$BODY$
LANGUAGE plpgsql;
I tried to separate the function in two different triggers, using the alphabetical order to order the execution, but without success, maybe because they are both run BEFORE...
Any idea ?
Thanks

Declare the foreign key in tableC as DEFERRABLE INITIALLY DEFERRED.
From the documentation:
DEFERRABLE NOT DEFERRABLE
This controls whether the constraint can be deferred. A constraint that is not deferrable will be checked immediately after every
command. Checking of constraints that are deferrable can be postponed
until the end of the transaction (using the SET CONSTRAINTS command).
NOT DEFERRABLE is the default. Currently, only UNIQUE, PRIMARY KEY,
EXCLUDE, and REFERENCES (foreign key) constraints accept this clause.
NOT NULL and CHECK constraints are not deferrable.
Btw. the first RETURN NEW; in the function body makes no sense.

First, it's impossible to have two RETURN statements in same flow of a function.
About your problem, there are many ways to achieve this. One of those is using a DEFERRABLE TRIGGER (an special type of trigger evaluated at end of transaction). Something like:
--Trigger function
CREATE OR REPLACE FUNCTION trigger1() RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO "tableB" VALUES (new.value);
INSERT INTO "tableC" (id, fkey) VALUES (new.something, new.value);
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
--Trigger raised at end of transaction. Take a look on 'CONSTRAINT' and 'INITIALLY DEFERRED' clauses.
CREATE CONSTRAINT TRIGGER deferred_trigger_1
AFTER INSERT OR UPDATE
ON "tableA"
INITIALLY DEFERRED
FOR EACH ROW
EXECUTE PROCEDURE trigger1();
More info here

Related

How to create a trigger in postgresql

I need to create a trigger in postgres where, when I add a record to table A, it automatically inserts its primary key value (which is auto incremented) into table B, the primary key of table A is the foreign key in table B.
I have tried doing it through pgadmin but it does not allow me to save. Could someone help out please.
First create the following trigger function.
CREATE OR REPLACE FUNCTION auto_insert() RETURNS TRIGGER AS
$BODY$
BEGIN
INSERT INTO
B(a_id)
VALUES(new.id);
RETURN new;
END;
$BODY$
language plpgsql;
And then attach the trigger function to table A.
CREATE TRIGGER auto_inserter
AFTER INSERT ON A
FOR EACH ROW
EXECUTE PROCEDURE auto_insert();

Is there any difference between a "not deferrable constraint trigger" and a normal "after event row level trigger"?

I get that for a normal constraint [NOT] DEFERRABLE {INITIALLY IMMEDIATE/INITIALLY DEFERRED} change when the constraint is checked: after each operation/ after each statement/ after each transaction. Also for deferrable trigger we can use "SET CONSTRAINTS".
But it seems that a "not deferrable constraint trigger" fires at the end of the statement for each row exactly as a normal "after event row level trigger" would do. Also for such trigger we can not use SET CONSTRAINTS because we have declared it as NOT DEFERRABLE.
For instance in this code the trigger copies everything that is visible into a table called "copy_example"
CREATE TABLE example (
ex INT);
CREATE TABLE copy_example (
ex_copy INT);
CREATE FUNCTION save_copy()
RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
BEGIN
INSERT INTO copy_example (SELECT * FROM example);
RETURN NEW;
END;
$$;
CREATE CONSTRAINT TRIGGER execute_copy
AFTER INSERT
ON example
NOT DEFERRABLE
FOR EACH ROW
EXECUTE FUNCTION save_copy();
INSERT INTO example VALUES (1), (2);
SELECT * FROM copy_example;
The code outputs a table with values (1, 2, 1, 2) and not (1, 1, 2), as a normal trigger would do.
So why bother to define a not deferrable constraint trigger instead of a normal trigger?
Is there any difference in their behavior?
No, it is pointless to define a non-deferrable constraint trigger.
The documentation describes the behavior exactly:
When the CONSTRAINT option is specified, this command creates a constraint trigger. This is the same as a regular trigger except that the timing of the trigger firing can be adjusted using SET CONSTRAINTS. Constraint triggers must be AFTER ROW triggers on plain tables (not foreign tables). They can be fired either at the end of the statement causing the triggering event, or at the end of the containing transaction; in the latter case they are said to be deferred. A pending deferred-trigger firing can also be forced to happen immediately by using SET CONSTRAINTS. Constraint triggers are expected to raise an exception when the constraints they implement are violated.

PostgreSQL foreign key violation inside transaction

I have a function with several queries inside, like this:
CREATE OR REPLACE FUNCTION public.myfunction(winningid integer, losingid integer)
RETURNS void
LANGUAGE plpgsql
AS $function$
begin
update partecipants set id_be=winningId where id_be=losingId;
...
many other updates and deletes regarding other tables
...
delete from business_entity where id_be=losingId;
end;
$function$
;
There is a foreign key between partecipants and business_entity:
ALTER TABLE partecipants ADD CONSTRAINT partecipants_fk FOREIGN KEY (id_be) REFERENCES business_entity(id_be)
Sometimes (like 1 in 1000 times) this function hangs with error:
error: update or delete on table "business_entity" violates foreign key constraint "partecipants_fk" on table "partecipants"
detail:
'Key (id_be)=(315017) is still referenced from table "partecipants".
If I run the same function a second time, ends without error.
How is this possible?
As a background information, other processes are using (often also with locks) the tables involved in the function while is running.
Concurrent transactions could add or modify rows in partecipants between your UPDATE and your DELETE so that the latter fails.
If you want to avoid that, do both in a single statement:
WITH dummy AS (
update partecipants set id_be=winningId where id_be=losingId
)
delete from business_entity where id_be=losingId;

How to properly emulate statement level triggers with access to data in postgres

I am using PostgreSQL as my database for a project at work. We use triggers in quite a few places to either maintain computed columns, or tables that essentially act as a materialized view.
All this worked just fine when simply utilizing row level triggers to keep all this in sync. However when we wrote scripts to periodically import our customers data into the database, we ran into issues with either performance or problems with number of locks in a single transaction.
To alleviate this I wanted to create a statement-level trigger with access to the modified rows (inserted, updated or deleted). However as this is not possible I instead created a BEFORE statement-level trigger that would create a temporary table. Then an AFTER row-level trigger that would insert the changed data into the temporary table. At last an AFTER statement-level trigger that would read the changes and perform necessary updates, and then drop the temporary table.
All this works just fine, assuming that within the triggers, no one would re-trigger the same flow again (as the temporary table would then already exist).
However I then learned that when using foreign key constraints with ON DELETE SET NULL, it is simply implemented with a system trigger that sets the column to NULL. This of course is not a problem at all, except for the fact that when you have several foreign key constraints like this on a single table, all referencing the same table (let's just call this files). When deleting a row from the files table, all these system level triggers to handle the ON DELETE SET NULL clause all fire at the same time, that is in parallel. Which presents a serious issue for me.
How would I go about implementing something like this? Here is a short SQL script to illustrate the problem:
CREATE TABLE files (
id serial PRIMARY KEY,
"name" TEXT NOT NULL
);
CREATE TABLE profiles (
id serial PRIMARY KEY,
NAME TEXT NOT NULL,
cv_file_id INT REFERENCES files(id) ON DELETE SET NULL,
photo_file_id INT REFERENCES files(id) ON DELETE SET NULL
);
CREATE TABLE profile_audit (
profile_id INT NOT NULL,
modified_at timestamptz NOT NULL
);
CREATE FUNCTION pre_stmt_create_temp_table()
RETURNS TRIGGER
AS $$
BEGIN
CREATE TEMPORARY TABLE tmp_modified_profiles (
id INT NOT NULL
) ON COMMIT DROP;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
CREATE FUNCTION insert_modified_profile_to_temp_table()
RETURNS TRIGGER
AS $$
BEGIN
INSERT INTO tmp_modified_profiles(id) VALUES (NEW.id);
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
CREATE FUNCTION post_stmt_insert_rows_and_drop_temp_table()
RETURNS TRIGGER
AS $$
BEGIN
INSERT INTO profile_audit (id, modified_at)
SELECT t.id, CURRENT_TIMESTAMP FROM tmp_modified_profiles t;
DROP TABLE tmp_modified_profiles;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER tr_create_working_table BEFORE UPDATE ON profiles FOR EACH STATEMENT EXECUTE PROCEDURE pre_stmt_create_temp_table();
CREATE TRIGGER tr_insert_row_to_working_table AFTER UPDATE ON profiles FOR EACH ROW EXECUTE PROCEDURE insert_modified_profile_to_temp_table();
CREATE TRIGGER tr_insert_modified_rows_and_drop_working_table AFTER UPDATE ON profiles FOR EACH STATEMENT EXECUTE PROCEDURE post_stmt_insert_rows_and_drop_temp_table();
INSERT INTO files ("name") VALUES ('photo.jpg'), ('my_cv.pdf');
INSERT INTO profiles ("name") VALUES ('John Doe');
DELETE FROM files WHERE "name" = 'photo.jpg';
It would be a serious hack, but meanwhile, until PostgreSQL 9.5 is out, I would try to use CONSTRAINT triggers deferred to the end of the transaction. I am not really sure this will work, but might be worth trying.
You could use a status column to track inserts and updates for your statement-level triggers.
In a BEFORE INSERT OR UPDATE row-level trigger:
SET NEW.status = TG_OP;
Now you can use statement-level AFTER triggers:
BEGIN
DO FUNNY THINGS
WHERE status = 'INSERT';
-- reset the status
UPDATE mytable
SET status = NULL
WHERE status = 'INSERT';
END;
However, if you want to deal with deletes as well, you'll need something like this in your row-level trigger:
INSERT INTO status_table (table_name, op, id) VALUES (TG_TABLE_NAME, TG_OP, OLD.id);
Then, in your statement-level AFTER trigger, you can go like:
BEGIN
DO FUNNY THINGS
WHERE id IN (SELECT id FROM status_table
WHERE table_name = TG_TABLE_NAME AND op = TG_OP); -- just an example
-- reset the status
DELETE FROM status_table
WHERE table_name = TG_TABLE_NAME AND op = TG_OP;
END;

Insert trigger to Update another table using PostgreSQL

I have a table named awards. How can I mount a Trigger in PostgreSQL where each insert in the table awards updates a different table?
Here we have two tables named table1 and table2. Using a trigger I'll update table2 on insertion into table1.
Create the tables
CREATE TABLE table1
(
id integer NOT NULL,
name character varying,
CONSTRAINT table1_pkey PRIMARY KEY (id)
)
CREATE TABLE table2
(
id integer NOT NULL,
name character varying
)
The Trigger Function
CREATE OR REPLACE FUNCTION function_copy() RETURNS TRIGGER AS
$BODY$
BEGIN
INSERT INTO
table2(id,name)
VALUES(new.id,new.name);
RETURN new;
END;
$BODY$
language plpgsql;
The Trigger
CREATE TRIGGER trig_copy
AFTER INSERT ON table1
FOR EACH ROW
EXECUTE PROCEDURE function_copy();
You want the documenation for PL/PgSQL triggers, which discusses just this case among others. The general documentation on triggers may also be useful.
You can use either a BEFORE or AFTER trigger for this. I'd probably use an AFTER trigger so that my trigger saw the final version of the row being inserted, though. You want FOR EACH ROW, of course.