2 41.10. Trigger Functions #
4 41.10.1. Triggers on Data Changes
5 41.10.2. Triggers on Events
7 PL/pgSQL can be used to define trigger functions on data changes or
8 database events. A trigger function is created with the CREATE FUNCTION
9 command, declaring it as a function with no arguments and a return type
10 of trigger (for data change triggers) or event_trigger (for database
11 event triggers). Special local variables named TG_something are
12 automatically defined to describe the condition that triggered the
15 41.10.1. Triggers on Data Changes #
17 A data change trigger is declared as a function with no arguments and a
18 return type of trigger. Note that the function must be declared with no
19 arguments even if it expects to receive some arguments specified in
20 CREATE TRIGGER — such arguments are passed via TG_ARGV, as described
23 When a PL/pgSQL function is called as a trigger, several special
24 variables are created automatically in the top-level block. They are:
27 new database row for INSERT/UPDATE operations in row-level
28 triggers. This variable is null in statement-level triggers and
29 for DELETE operations.
32 old database row for UPDATE/DELETE operations in row-level
33 triggers. This variable is null in statement-level triggers and
34 for INSERT operations.
37 name of the trigger which fired.
40 BEFORE, AFTER, or INSTEAD OF, depending on the trigger's
44 ROW or STATEMENT, depending on the trigger's definition.
47 operation for which the trigger was fired: INSERT, UPDATE,
50 TG_RELID oid (references pg_class.oid) #
51 object ID of the table that caused the trigger invocation.
54 table that caused the trigger invocation. This is now
55 deprecated, and could disappear in a future release. Use
56 TG_TABLE_NAME instead.
59 table that caused the trigger invocation.
61 TG_TABLE_SCHEMA name #
62 schema of the table that caused the trigger invocation.
65 number of arguments given to the trigger function in the CREATE
69 arguments from the CREATE TRIGGER statement. The index counts
70 from 0. Invalid indexes (less than 0 or greater than or equal to
71 tg_nargs) result in a null value.
73 A trigger function must return either NULL or a record/row value having
74 exactly the structure of the table the trigger was fired for.
76 Row-level triggers fired BEFORE can return null to signal the trigger
77 manager to skip the rest of the operation for this row (i.e.,
78 subsequent triggers are not fired, and the INSERT/UPDATE/DELETE does
79 not occur for this row). If a nonnull value is returned then the
80 operation proceeds with that row value. Returning a row value different
81 from the original value of NEW alters the row that will be inserted or
82 updated. Thus, if the trigger function wants the triggering action to
83 succeed normally without altering the row value, NEW (or a value equal
84 thereto) has to be returned. To alter the row to be stored, it is
85 possible to replace single values directly in NEW and return the
86 modified NEW, or to build a complete new record/row to return. In the
87 case of a before-trigger on DELETE, the returned value has no direct
88 effect, but it has to be nonnull to allow the trigger action to
89 proceed. Note that NEW is null in DELETE triggers, so returning that is
90 usually not sensible. The usual idiom in DELETE triggers is to return
93 INSTEAD OF triggers (which are always row-level triggers, and may only
94 be used on views) can return null to signal that they did not perform
95 any updates, and that the rest of the operation for this row should be
96 skipped (i.e., subsequent triggers are not fired, and the row is not
97 counted in the rows-affected status for the surrounding
98 INSERT/UPDATE/DELETE). Otherwise a nonnull value should be returned, to
99 signal that the trigger performed the requested operation. For INSERT
100 and UPDATE operations, the return value should be NEW, which the
101 trigger function may modify to support INSERT RETURNING and UPDATE
102 RETURNING (this will also affect the row value passed to any subsequent
103 triggers, or passed to a special EXCLUDED alias reference within an
104 INSERT statement with an ON CONFLICT DO UPDATE clause). For DELETE
105 operations, the return value should be OLD.
107 The return value of a row-level trigger fired AFTER or a
108 statement-level trigger fired BEFORE or AFTER is always ignored; it
109 might as well be null. However, any of these types of triggers might
110 still abort the entire operation by raising an error.
112 Example 41.3 shows an example of a trigger function in PL/pgSQL.
114 Example 41.3. A PL/pgSQL Trigger Function
116 This example trigger ensures that any time a row is inserted or updated
117 in the table, the current user name and time are stamped into the row.
118 And it checks that an employee's name is given and that the salary is a
127 CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
129 -- Check that empname and salary are given
130 IF NEW.empname IS NULL THEN
131 RAISE EXCEPTION 'empname cannot be null';
133 IF NEW.salary IS NULL THEN
134 RAISE EXCEPTION '% cannot have null salary', NEW.empname;
137 -- Who works for us when they must pay for it?
138 IF NEW.salary < 0 THEN
139 RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
142 -- Remember who changed the payroll when
143 NEW.last_date := current_timestamp;
144 NEW.last_user := current_user;
147 $emp_stamp$ LANGUAGE plpgsql;
149 CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
150 FOR EACH ROW EXECUTE FUNCTION emp_stamp();
152 Another way to log changes to a table involves creating a new table
153 that holds a row for each insert, update, or delete that occurs. This
154 approach can be thought of as auditing changes to a table. Example 41.4
155 shows an example of an audit trigger function in PL/pgSQL.
157 Example 41.4. A PL/pgSQL Trigger Function for Auditing
159 This example trigger ensures that any insert, update or delete of a row
160 in the emp table is recorded (i.e., audited) in the emp_audit table.
161 The current time and user name are stamped into the row, together with
162 the type of operation performed on it.
164 empname text NOT NULL,
168 CREATE TABLE emp_audit(
169 operation char(1) NOT NULL,
170 stamp timestamp NOT NULL,
171 userid text NOT NULL,
172 empname text NOT NULL,
176 CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$
179 -- Create a row in emp_audit to reflect the operation performed on emp,
180 -- making use of the special variable TG_OP to work out the operation.
182 IF (TG_OP = 'DELETE') THEN
183 INSERT INTO emp_audit SELECT 'D', now(), current_user, OLD.*;
184 ELSIF (TG_OP = 'UPDATE') THEN
185 INSERT INTO emp_audit SELECT 'U', now(), current_user, NEW.*;
186 ELSIF (TG_OP = 'INSERT') THEN
187 INSERT INTO emp_audit SELECT 'I', now(), current_user, NEW.*;
189 RETURN NULL; -- result is ignored since this is an AFTER trigger
191 $emp_audit$ LANGUAGE plpgsql;
193 CREATE TRIGGER emp_audit
194 AFTER INSERT OR UPDATE OR DELETE ON emp
195 FOR EACH ROW EXECUTE FUNCTION process_emp_audit();
197 A variation of the previous example uses a view joining the main table
198 to the audit table, to show when each entry was last modified. This
199 approach still records the full audit trail of changes to the table,
200 but also presents a simplified view of the audit trail, showing just
201 the last modified timestamp derived from the audit trail for each
202 entry. Example 41.5 shows an example of an audit trigger on a view in
205 Example 41.5. A PL/pgSQL View Trigger Function for Auditing
207 This example uses a trigger on the view to make it updatable, and
208 ensure that any insert, update or delete of a row in the view is
209 recorded (i.e., audited) in the emp_audit table. The current time and
210 user name are recorded, together with the type of operation performed,
211 and the view displays the last modified time of each row.
213 empname text PRIMARY KEY,
217 CREATE TABLE emp_audit(
218 operation char(1) NOT NULL,
219 userid text NOT NULL,
220 empname text NOT NULL,
222 stamp timestamp NOT NULL
225 CREATE VIEW emp_view AS
228 max(ea.stamp) AS last_updated
230 LEFT JOIN emp_audit ea ON ea.empname = e.empname
233 CREATE OR REPLACE FUNCTION update_emp_view() RETURNS TRIGGER AS $$
236 -- Perform the required operation on emp, and create a row in emp_audit
237 -- to reflect the change made to emp.
239 IF (TG_OP = 'DELETE') THEN
240 DELETE FROM emp WHERE empname = OLD.empname;
241 IF NOT FOUND THEN RETURN NULL; END IF;
243 OLD.last_updated = now();
244 INSERT INTO emp_audit VALUES('D', current_user, OLD.*);
246 ELSIF (TG_OP = 'UPDATE') THEN
247 UPDATE emp SET salary = NEW.salary WHERE empname = OLD.empname;
248 IF NOT FOUND THEN RETURN NULL; END IF;
250 NEW.last_updated = now();
251 INSERT INTO emp_audit VALUES('U', current_user, NEW.*);
253 ELSIF (TG_OP = 'INSERT') THEN
254 INSERT INTO emp VALUES(NEW.empname, NEW.salary);
256 NEW.last_updated = now();
257 INSERT INTO emp_audit VALUES('I', current_user, NEW.*);
263 CREATE TRIGGER emp_audit
264 INSTEAD OF INSERT OR UPDATE OR DELETE ON emp_view
265 FOR EACH ROW EXECUTE FUNCTION update_emp_view();
267 One use of triggers is to maintain a summary table of another table.
268 The resulting summary can be used in place of the original table for
269 certain queries — often with vastly reduced run times. This technique
270 is commonly used in Data Warehousing, where the tables of measured or
271 observed data (called fact tables) might be extremely large.
272 Example 41.6 shows an example of a trigger function in PL/pgSQL that
273 maintains a summary table for a fact table in a data warehouse.
275 Example 41.6. A PL/pgSQL Trigger Function for Maintaining a Summary
278 The schema detailed here is partly based on the Grocery Store example
279 from The Data Warehouse Toolkit by Ralph Kimball.
281 -- Main tables - time dimension and sales fact.
283 CREATE TABLE time_dimension (
284 time_key integer NOT NULL,
285 day_of_week integer NOT NULL,
286 day_of_month integer NOT NULL,
287 month integer NOT NULL,
288 quarter integer NOT NULL,
289 year integer NOT NULL
291 CREATE UNIQUE INDEX time_dimension_key ON time_dimension(time_key);
293 CREATE TABLE sales_fact (
294 time_key integer NOT NULL,
295 product_key integer NOT NULL,
296 store_key integer NOT NULL,
297 amount_sold numeric(12,2) NOT NULL,
298 units_sold integer NOT NULL,
299 amount_cost numeric(12,2) NOT NULL
301 CREATE INDEX sales_fact_time ON sales_fact(time_key);
304 -- Summary table - sales by time.
306 CREATE TABLE sales_summary_bytime (
307 time_key integer NOT NULL,
308 amount_sold numeric(15,2) NOT NULL,
309 units_sold numeric(12) NOT NULL,
310 amount_cost numeric(15,2) NOT NULL
312 CREATE UNIQUE INDEX sales_summary_bytime_key ON sales_summary_bytime(time_key);
315 -- Function and trigger to amend summarized column(s) on UPDATE, INSERT, DELETE.
317 CREATE OR REPLACE FUNCTION maint_sales_summary_bytime() RETURNS TRIGGER
318 AS $maint_sales_summary_bytime$
320 delta_time_key integer;
321 delta_amount_sold numeric(15,2);
322 delta_units_sold numeric(12);
323 delta_amount_cost numeric(15,2);
326 -- Work out the increment/decrement amount(s).
327 IF (TG_OP = 'DELETE') THEN
329 delta_time_key = OLD.time_key;
330 delta_amount_sold = -1 * OLD.amount_sold;
331 delta_units_sold = -1 * OLD.units_sold;
332 delta_amount_cost = -1 * OLD.amount_cost;
334 ELSIF (TG_OP = 'UPDATE') THEN
336 -- forbid updates that change the time_key -
337 -- (probably not too onerous, as DELETE + INSERT is how most
338 -- changes will be made).
339 IF ( OLD.time_key != NEW.time_key) THEN
340 RAISE EXCEPTION 'Update of time_key : % -> % not allowed',
341 OLD.time_key, NEW.time_key
345 delta_time_key = OLD.time_key;
346 delta_amount_sold = NEW.amount_sold - OLD.amount_sold;
347 delta_units_sold = NEW.units_sold - OLD.units_sold;
348 delta_amount_cost = NEW.amount_cost - OLD.amount_cost;
350 ELSIF (TG_OP = 'INSERT') THEN
352 delta_time_key = NEW.time_key;
353 delta_amount_sold = NEW.amount_sold;
354 delta_units_sold = NEW.units_sold;
355 delta_amount_cost = NEW.amount_cost;
360 -- Insert or update the summary row with the new values.
363 UPDATE sales_summary_bytime
364 SET amount_sold = amount_sold + delta_amount_sold,
365 units_sold = units_sold + delta_units_sold,
366 amount_cost = amount_cost + delta_amount_cost
367 WHERE time_key = delta_time_key;
369 EXIT insert_update WHEN found;
372 INSERT INTO sales_summary_bytime (
387 WHEN UNIQUE_VIOLATION THEN
390 END LOOP insert_update;
395 $maint_sales_summary_bytime$ LANGUAGE plpgsql;
397 CREATE TRIGGER maint_sales_summary_bytime
398 AFTER INSERT OR UPDATE OR DELETE ON sales_fact
399 FOR EACH ROW EXECUTE FUNCTION maint_sales_summary_bytime();
401 INSERT INTO sales_fact VALUES(1,1,1,10,3,15);
402 INSERT INTO sales_fact VALUES(1,2,1,20,5,35);
403 INSERT INTO sales_fact VALUES(2,2,1,40,15,135);
404 INSERT INTO sales_fact VALUES(2,3,1,10,1,13);
405 SELECT * FROM sales_summary_bytime;
406 DELETE FROM sales_fact WHERE product_key = 1;
407 SELECT * FROM sales_summary_bytime;
408 UPDATE sales_fact SET units_sold = units_sold * 2;
409 SELECT * FROM sales_summary_bytime;
411 AFTER triggers can also make use of transition tables to inspect the
412 entire set of rows changed by the triggering statement. The CREATE
413 TRIGGER command assigns names to one or both transition tables, and
414 then the function can refer to those names as though they were
415 read-only temporary tables. Example 41.7 shows an example.
417 Example 41.7. Auditing with Transition Tables
419 This example produces the same results as Example 41.4, but instead of
420 using a trigger that fires for every row, it uses a trigger that fires
421 once per statement, after collecting the relevant information in a
422 transition table. This can be significantly faster than the row-trigger
423 approach when the invoking statement has modified many rows. Notice
424 that we must make a separate trigger declaration for each kind of
425 event, since the REFERENCING clauses must be different for each case.
426 But this does not stop us from using a single trigger function if we
427 choose. (In practice, it might be better to use three separate
428 functions and avoid the run-time tests on TG_OP.)
430 empname text NOT NULL,
434 CREATE TABLE emp_audit(
435 operation char(1) NOT NULL,
436 stamp timestamp NOT NULL,
437 userid text NOT NULL,
438 empname text NOT NULL,
442 CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$
445 -- Create rows in emp_audit to reflect the operations performed on emp,
446 -- making use of the special variable TG_OP to work out the operation.
448 IF (TG_OP = 'DELETE') THEN
449 INSERT INTO emp_audit
450 SELECT 'D', now(), current_user, o.* FROM old_table o;
451 ELSIF (TG_OP = 'UPDATE') THEN
452 INSERT INTO emp_audit
453 SELECT 'U', now(), current_user, n.* FROM new_table n;
454 ELSIF (TG_OP = 'INSERT') THEN
455 INSERT INTO emp_audit
456 SELECT 'I', now(), current_user, n.* FROM new_table n;
458 RETURN NULL; -- result is ignored since this is an AFTER trigger
460 $emp_audit$ LANGUAGE plpgsql;
462 CREATE TRIGGER emp_audit_ins
464 REFERENCING NEW TABLE AS new_table
465 FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
466 CREATE TRIGGER emp_audit_upd
468 REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
469 FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
470 CREATE TRIGGER emp_audit_del
472 REFERENCING OLD TABLE AS old_table
473 FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
475 41.10.2. Triggers on Events #
477 PL/pgSQL can be used to define event triggers. PostgreSQL requires that
478 a function that is to be called as an event trigger must be declared as
479 a function with no arguments and a return type of event_trigger.
481 When a PL/pgSQL function is called as an event trigger, several special
482 variables are created automatically in the top-level block. They are:
485 event the trigger is fired for.
488 command tag for which the trigger is fired.
490 Example 41.8 shows an example of an event trigger function in PL/pgSQL.
492 Example 41.8. A PL/pgSQL Event Trigger Function
494 This example trigger simply raises a NOTICE message each time a
495 supported command is executed.
496 CREATE OR REPLACE FUNCTION snitch() RETURNS event_trigger AS $$
498 RAISE NOTICE 'snitch: % %', tg_event, tg_tag;
502 CREATE EVENT TRIGGER snitch ON ddl_command_start EXECUTE FUNCTION snitch();