]> begriffs open source - ai-pg/blob - full-docs/txt/plpgsql-trigger.txt
Convert HTML docs to more streamlined TXT
[ai-pg] / full-docs / txt / plpgsql-trigger.txt
1
2 41.10. Trigger Functions #
3
4    41.10.1. Triggers on Data Changes
5    41.10.2. Triggers on Events
6
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
13    call.
14
15 41.10.1. Triggers on Data Changes #
16
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
21    below.
22
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:
25
26    NEW record #
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.
30
31    OLD record #
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.
35
36    TG_NAME name #
37           name of the trigger which fired.
38
39    TG_WHEN text #
40           BEFORE, AFTER, or INSTEAD OF, depending on the trigger's
41           definition.
42
43    TG_LEVEL text #
44           ROW or STATEMENT, depending on the trigger's definition.
45
46    TG_OP text #
47           operation for which the trigger was fired: INSERT, UPDATE,
48           DELETE, or TRUNCATE.
49
50    TG_RELID oid (references pg_class.oid) #
51           object ID of the table that caused the trigger invocation.
52
53    TG_RELNAME name #
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.
57
58    TG_TABLE_NAME name #
59           table that caused the trigger invocation.
60
61    TG_TABLE_SCHEMA name #
62           schema of the table that caused the trigger invocation.
63
64    TG_NARGS integer #
65           number of arguments given to the trigger function in the CREATE
66           TRIGGER statement.
67
68    TG_ARGV text[] #
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.
72
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.
75
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
91    OLD.
92
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.
106
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.
111
112    Example 41.3 shows an example of a trigger function in PL/pgSQL.
113
114    Example 41.3. A PL/pgSQL Trigger Function
115
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
119    positive value.
120 CREATE TABLE emp (
121     empname           text,
122     salary            integer,
123     last_date         timestamp,
124     last_user         text
125 );
126
127 CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
128     BEGIN
129         -- Check that empname and salary are given
130         IF NEW.empname IS NULL THEN
131             RAISE EXCEPTION 'empname cannot be null';
132         END IF;
133         IF NEW.salary IS NULL THEN
134             RAISE EXCEPTION '% cannot have null salary', NEW.empname;
135         END IF;
136
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;
140         END IF;
141
142         -- Remember who changed the payroll when
143         NEW.last_date := current_timestamp;
144         NEW.last_user := current_user;
145         RETURN NEW;
146     END;
147 $emp_stamp$ LANGUAGE plpgsql;
148
149 CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
150     FOR EACH ROW EXECUTE FUNCTION emp_stamp();
151
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.
156
157    Example 41.4. A PL/pgSQL Trigger Function for Auditing
158
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.
163 CREATE TABLE emp (
164     empname           text NOT NULL,
165     salary            integer
166 );
167
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,
173     salary            integer
174 );
175
176 CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$
177     BEGIN
178         --
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.
181         --
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.*;
188         END IF;
189         RETURN NULL; -- result is ignored since this is an AFTER trigger
190     END;
191 $emp_audit$ LANGUAGE plpgsql;
192
193 CREATE TRIGGER emp_audit
194 AFTER INSERT OR UPDATE OR DELETE ON emp
195     FOR EACH ROW EXECUTE FUNCTION process_emp_audit();
196
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
203    PL/pgSQL.
204
205    Example 41.5. A PL/pgSQL View Trigger Function for Auditing
206
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.
212 CREATE TABLE emp (
213     empname           text PRIMARY KEY,
214     salary            integer
215 );
216
217 CREATE TABLE emp_audit(
218     operation         char(1)   NOT NULL,
219     userid            text      NOT NULL,
220     empname           text      NOT NULL,
221     salary            integer,
222     stamp             timestamp NOT NULL
223 );
224
225 CREATE VIEW emp_view AS
226     SELECT e.empname,
227            e.salary,
228            max(ea.stamp) AS last_updated
229       FROM emp e
230       LEFT JOIN emp_audit ea ON ea.empname = e.empname
231      GROUP BY 1, 2;
232
233 CREATE OR REPLACE FUNCTION update_emp_view() RETURNS TRIGGER AS $$
234     BEGIN
235         --
236         -- Perform the required operation on emp, and create a row in emp_audit
237         -- to reflect the change made to emp.
238         --
239         IF (TG_OP = 'DELETE') THEN
240             DELETE FROM emp WHERE empname = OLD.empname;
241             IF NOT FOUND THEN RETURN NULL; END IF;
242
243             OLD.last_updated = now();
244             INSERT INTO emp_audit VALUES('D', current_user, OLD.*);
245             RETURN 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;
249
250             NEW.last_updated = now();
251             INSERT INTO emp_audit VALUES('U', current_user, NEW.*);
252             RETURN NEW;
253         ELSIF (TG_OP = 'INSERT') THEN
254             INSERT INTO emp VALUES(NEW.empname, NEW.salary);
255
256             NEW.last_updated = now();
257             INSERT INTO emp_audit VALUES('I', current_user, NEW.*);
258             RETURN NEW;
259         END IF;
260     END;
261 $$ LANGUAGE plpgsql;
262
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();
266
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.
274
275    Example 41.6. A PL/pgSQL Trigger Function for Maintaining a Summary
276    Table
277
278    The schema detailed here is partly based on the Grocery Store example
279    from The Data Warehouse Toolkit by Ralph Kimball.
280 --
281 -- Main tables - time dimension and sales fact.
282 --
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
290 );
291 CREATE UNIQUE INDEX time_dimension_key ON time_dimension(time_key);
292
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
300 );
301 CREATE INDEX sales_fact_time ON sales_fact(time_key);
302
303 --
304 -- Summary table - sales by time.
305 --
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
311 );
312 CREATE UNIQUE INDEX sales_summary_bytime_key ON sales_summary_bytime(time_key);
313
314 --
315 -- Function and trigger to amend summarized column(s) on UPDATE, INSERT, DELETE.
316 --
317 CREATE OR REPLACE FUNCTION maint_sales_summary_bytime() RETURNS TRIGGER
318 AS $maint_sales_summary_bytime$
319     DECLARE
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);
324     BEGIN
325
326         -- Work out the increment/decrement amount(s).
327         IF (TG_OP = 'DELETE') THEN
328
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;
333
334         ELSIF (TG_OP = 'UPDATE') THEN
335
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
342 ;
343             END IF;
344
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;
349
350         ELSIF (TG_OP = 'INSERT') THEN
351
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;
356
357         END IF;
358
359
360         -- Insert or update the summary row with the new values.
361         <<insert_update>>
362         LOOP
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;
368
369             EXIT insert_update WHEN found;
370
371             BEGIN
372                 INSERT INTO sales_summary_bytime (
373                             time_key,
374                             amount_sold,
375                             units_sold,
376                             amount_cost)
377                     VALUES (
378                             delta_time_key,
379                             delta_amount_sold,
380                             delta_units_sold,
381                             delta_amount_cost
382                            );
383
384                 EXIT insert_update;
385
386             EXCEPTION
387                 WHEN UNIQUE_VIOLATION THEN
388                     -- do nothing
389             END;
390         END LOOP insert_update;
391
392         RETURN NULL;
393
394     END;
395 $maint_sales_summary_bytime$ LANGUAGE plpgsql;
396
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();
400
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;
410
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.
416
417    Example 41.7. Auditing with Transition Tables
418
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.)
429 CREATE TABLE emp (
430     empname           text NOT NULL,
431     salary            integer
432 );
433
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,
439     salary            integer
440 );
441
442 CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$
443     BEGIN
444         --
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.
447         --
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;
457         END IF;
458         RETURN NULL; -- result is ignored since this is an AFTER trigger
459     END;
460 $emp_audit$ LANGUAGE plpgsql;
461
462 CREATE TRIGGER emp_audit_ins
463     AFTER INSERT ON emp
464     REFERENCING NEW TABLE AS new_table
465     FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
466 CREATE TRIGGER emp_audit_upd
467     AFTER UPDATE ON emp
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
471     AFTER DELETE ON emp
472     REFERENCING OLD TABLE AS old_table
473     FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
474
475 41.10.2. Triggers on Events #
476
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.
480
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:
483
484    TG_EVENT text #
485           event the trigger is fired for.
486
487    TG_TAG text #
488           command tag for which the trigger is fired.
489
490    Example 41.8 shows an example of an event trigger function in PL/pgSQL.
491
492    Example 41.8. A PL/pgSQL Event Trigger Function
493
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 $$
497 BEGIN
498     RAISE NOTICE 'snitch: % %', tg_event, tg_tag;
499 END;
500 $$ LANGUAGE plpgsql;
501
502 CREATE EVENT TRIGGER snitch ON ddl_command_start EXECUTE FUNCTION snitch();