I have a deferred AFTER UPDATE trigger on a table, set to fire when a certain column is updated. It’s an integer type I’m using as a counter.
I’m not 100% certain but it looks like if I increment that particular column 100 times during a transaction, the trigger is queued up and executed 100 times at the end of the transaction.
I would like the trigger to only be scheduled once per row no matter how many times I’ve incremented that column.
Can I do that somehow?
Alternatively if triggered triggers must queue up regardless if they are duplicates, can I clear this queue during the first run of the trigger?
Version of Postgres is 9.1. Here’s what I got:
CREATE CONSTRAINT TRIGGER counter_change
AFTER UPDATE OF "Counter" ON "table"
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE PROCEDURE counter_change();
CREATE OR REPLACE FUNCTION counter_change()
RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
BEGIN
PERFORM some_expensive_procedure(NEW."id");
RETURN NEW;
END;$$;
This is a tricky problem. But it can be done with per-column triggers and conditional trigger execution introduced in PostgreSQL 9.0.
You need an "updated" flag per row for this solution. Use a
booleancolumn in the same table for simplicity. But it could be in another table or even a temporary table per transaction.The expensive payload is executed once per row where the counter is updated (once or multiple time).
This should also perform well, because …
Consider the following
Demo
Tested in PostgreSQL 9.1 with a separate schema
xas test environment.Tables and dummy rows
Insert two rows to demonstrate it works with multiple rows:
Trigger functions and Triggers
1.) Execute expensive payload
2.) Flag row as updated.
3.) Reset "updated" flag.
Trigger names are relevant! Called for the same event they are executed in alphabetical order.
1.) Payload, only if not "updated" yet:
2.) Flag row as updated, only if not "updated" yet:
3.) Reset Flag. No endless loop because of trigger condition.
Test
Run
UPDATE&SELECTseparately to see the deferred effect. If executed together (in one transaction) the SELECT will show the newtbl.counterbut the oldtbl2.trig_exec_count.Now, update the counter multiple times (in one transaction). The payload will only be executed once. Voilá!