I've been looking at the documentation of postgresql triggers, but it seems to only show examples for row-level triggers, but I can't find an example for a statement-level trigger.
In particular, it is not quite clear how to iterate in the update/inserted rows in a single statement, since NEW
is for a single record.
A statement-level trigger is fired whenever a trigger event occurs on a table regardless of how many rows are affected. In other words, a statement-level trigger executes once for each transaction. For example, if you update 1000 rows in a table, then a statement-level trigger on that table would only be executed once.
The FOR EACH ROW option determines whether the trigger is a row trigger or a statement trigger. If you specify FOR EACH ROW , then the trigger fires once for each row of the table that is affected by the triggering statement.
Syntax. CREATE TRIGGER trigger_name [BEFORE|AFTER|INSTEAD OF] event_name ON table_name [ -- Trigger logic goes here.... ]; Here, event_name could be INSERT, DELETE, UPDATE, and TRUNCATE database operation on the mentioned table table_name. You can optionally specify FOR EACH ROW after table name.
OLD
and NEW
are null or not defined in a statement-level trigger. Per documentation:
NEW
Data type
RECORD
; variable holding the new database row forINSERT
/UPDATE
operations in row-level triggers. This variable is null in statement-level triggers and forDELETE
operations.
OLD
Data type RECORD; variable holding the old database row for
UPDATE
/DELETE
operations in row-level triggers. This variable is null in statement-level triggers and forINSERT
operations.
Bold emphasis mine.
Up to Postgres 10 this read slightly different, much to the same effect, though:
... This variable is unassigned in statement-level triggers. ...
While those record variables are still of no use for statement level triggers, a new feature very much is:
Postgres 10 introduced transition tables. Those allow access to the whole set of affected rows. The manual:
AFTER
triggers can also make use of transition tables to inspect the entire set of rows changed by the triggering statement. TheCREATE TRIGGER
command assigns names to one or both transition tables, and then the function can refer to those names as though they were read-only temporary tables. Example 43.7 shows an example.
Follow the link to the manual for code examples.
Before the advent of transition tables, those were even less common. A useful example is to send notifications after certain DML commands.
Here is a basic version of what I use:
-- Generic trigger function, can be used for multiple triggers:
CREATE OR REPLACE FUNCTION trg_notify_after()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
PERFORM pg_notify(TG_TABLE_NAME, TG_OP);
RETURN NULL;
END
$func$;
-- Trigger
CREATE TRIGGER notify_after
AFTER INSERT OR UPDATE OR DELETE ON my_tbl
FOR EACH STATEMENT
EXECUTE PROCEDURE trg_notify_after();
For Postgres 11 or later use the equivalent, less confusing syntax:
...
EXECUTE FUNCTION trg_notify_after();
See:
Well, here are some examples of statement-level triggers.
Table:
CREATE TABLE public.test (
number integer NOT NULL,
text character varying(50)
);
Trigger function:OLD
and NEW
are still NULL
The return value can also be always left NULL
.
CREATE OR REPLACE FUNCTION public.tr_test_for_each_statement()
RETURNS trigger
LANGUAGE plpgsql
AS
$$
DECLARE
x_rec record;
BEGIN
raise notice '=operation: % =', TG_OP;
IF (TG_OP = 'UPDATE' OR TG_OP = 'DELETE') THEN
FOR x_rec IN SELECT * FROM old_table LOOP
raise notice 'OLD: %', x_rec;
END loop;
END IF;
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
FOR x_rec IN SELECT * FROM new_table LOOP
raise notice 'NEW: %', x_rec;
END loop;
END IF;
RETURN NULL;
END;
$$;
Settings statement-level triggers
Only AFTER
and only one event is supported.
CREATE TRIGGER tr_test_for_each_statement_insert
AFTER INSERT ON public.test
REFERENCING NEW TABLE AS new_table
FOR EACH STATEMENT
EXECUTE PROCEDURE public.tr_test_for_each_statement();
CREATE TRIGGER tr_test_for_each_statement_update
AFTER UPDATE ON public.test
REFERENCING NEW TABLE AS new_table OLD TABLE AS old_table
FOR EACH STATEMENT
EXECUTE PROCEDURE public.tr_test_for_each_statement();
CREATE TRIGGER tr_test_for_each_statement_delete
AFTER DELETE ON public.test
REFERENCING OLD TABLE AS old_table
FOR EACH STATEMENT
EXECUTE PROCEDURE public.tr_test_for_each_statement();
Examples:
INSERT INTO public.test(number, text) VALUES (1, 'a');
=operation: INSERT =
NEW: (1,a)
INSERT INTO public.test(number, text) VALUES (2, 'b'), (3, 'b');
=operation: INSERT =
NEW: (2,b)
NEW: (3,b)
UPDATE public.test SET number = number + 1 WHERE text = 'a';
=operation: UPDATE =
OLD: (1,a)
NEW: (2,a)
UPDATE public.test SET number = number + 10 WHERE text = 'b';
=operation: UPDATE =
OLD: (2,b)
OLD: (3,b)
NEW: (12,b)
NEW: (13,b)
DELETE FROM public.test;
=operation: DELETE =
OLD: (2,a)
OLD: (12,b)
OLD: (13,b)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With