Triggers
INSERT, UPDATE, or DELETE — on a table, without the application having to explicitly call anything.Timing and granularity
Two independent choices control exactly when and how often a trigger fires:
Dimension | Options | Meaning |
|---|---|---|
Timing |
|
|
Granularity |
| Row-level fires once per affected row; statement-level fires once total for the whole statement, regardless of how many rows it touched |
PostgreSQL's two-part trigger system
CREATE TRIGGER statement that declares when and on what the trigger fires, and a separately-defined trigger function — written in PL/pgSQL — that contains the actual logic and must declare its return type as the special TRIGGER pseudo-type.CREATE TRIGGER statements.Worked example: auto-updating an updated_at column
updated_at timestamp column current automatically, without every application code path having to remember to set it.1. The trigger function
CREATE FUNCTION set_updated_at() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END; $$;
2. The trigger that calls it
CREATE TRIGGER trg_products_set_updated_at BEFORE UPDATE ON products FOR EACH ROW EXECUTE FUNCTION set_updated_at();
products automatically stamps updated_at, regardless of which application code path performed the update:UPDATE products SET price = 24.99 WHERE id = 12; -- updated_at is set to the current time automatically, without being -- mentioned anywhere in this statement
Another classic use case: auditing
CREATE FUNCTION audit_products_change() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN INSERT INTO products_audit (product_id, changed_at, old_price, new_price) VALUES (OLD.id, now(), OLD.price, NEW.price); RETURN NEW; END; $$; CREATE TRIGGER trg_products_audit AFTER UPDATE ON products FOR EACH ROW WHEN (OLD.price IS DISTINCT FROM NEW.price) EXECUTE FUNCTION audit_products_change();
products_audit every time a product's price actually changes, purely as a side effect of the UPDATE statement — no application code needs to know the audit table exists.UPDATE products SET price = ... can silently cascade into writes on other tables, extra validation, or even cancellation of the update, none of which is visible at the call site. Use triggers judiciously — for cross-cutting concerns like timestamps and audit trails they're a great fit, but piling up business logic in triggers can make a codebase very difficult to reason about.Triggers run automatically on
INSERT/UPDATE/DELETE, withBEFORE,AFTER, orINSTEAD OFtiming.Row-level (
FOR EACH ROW) fires per affected row; statement-level (FOR EACH STATEMENT) fires once per statement.PostgreSQL splits a trigger into a
CREATE TRIGGERdeclaration and a separate PL/pgSQL trigger function returningTRIGGER.Great for cross-cutting concerns like
updated_attimestamps and audit logs; risky as a home for core business logic.