I have a table a
with 3 triggers that insert, update, or delete corresponding rows in b
whenever a row in a
is inserted, updated, or deleted. All 3 triggers use the same trigger function p
.
CREATE OR REPLACE FUNCTION p ()
RETURNS TRIGGER
AS $$
BEGIN
IF (TG_OP = 'INSERT') THEN
-- INSERT INTO b ...
RETURN NEW;
ELSIF (TG_OP = 'UPDATE') THEN
-- UPDATE b ...
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
-- DELETE FROM b ...
RETURN NEW;
ELSE
RETURN NULL;
END IF;
END;
$$ LANGUAGE PLPGSQL;
CREATE TRIGGER i AFTER INSERT ON a FOR EACH ROW EXECUTE PROCEDURE p ();
CREATE TRIGGER u AFTER UPDATE ON a FOR EACH ROW EXECUTE PROCEDURE p ();
CREATE TRIGGER d AFTER DELETE ON a FOR EACH ROW EXECUTE PROCEDURE p ();
a
also has a foreign key a1
into c
(with primary key c1
), and I would like to alter p
in such a way that it enters the IF
/ELSIF
branches also depending on a column c2
in c
: if that joined column changed, enter the INSERT
and UPDATE
branches; if it stayed the same, enter the UPDATE
branch. In effect, something like this:
IF (TG_OP = 'INSERT') OR ((TG_OP = 'UPDATE') AND (oldC.c2 <> newC.c2)) THEN
-- ...
ELSIF (TG_OP = 'UPDATE') OR (oldC.c2 = newC.c2) THEN
-- ...
ELSIF (TG_OP = 'DELETE') OR ((TG_OP = 'UPDATE') AND (oldC.c2 <> newC.c2)) THEN
-- ...
ELSE
-- ...
END IF;
where oldC
and newC
would result from joins similar to these (with approx. syntax):
SELECT oldC.* FROM a, c AS oldC WHERE OLD.a1 = c.c1;
SELECT newC.* FROM a, c AS newC WHERE NEW.a1 = c.c1;
So what is needed in effect are two joins outside the IF
statement, which would allow it to refer to oldC
and newC
(or something analogous). Is this possible and how would the altered version of p
look (with correct PostgreSQL syntax)?
First off, there is no NEW
in case of a DELETE
, so RETURN NEW;
doesn't make sense. This would raise an exception before Postgres 11, where this was changed:
- In PL/pgSQL trigger functions, the
OLD
andNEW
variables now read as NULL when not assigned (Tom Lane)Previously, references to these variables could be parsed but not executed.
It doesn't matter what you return for AFTER
triggers anyway. Might as well be RETURN NULL;
There is no OLD
in case of an INSERT
, either.
You would only need a single trigger the way you have it right now:
CREATE TRIGGER a_i_u_d -- *one* trigger
AFTER INSERT OR UPDATE OR DELETE ON a
FOR EACH ROW EXECUTE FUNCTION p ();
However, I suggest separate trigger functions for INSERT
, UPDATE
and DELETE
to avoid complications. Then you need three separate triggers, each calling its respective trigger function.
The case you want to add can only affect UPDATE
. Nothing can "change" like you describe with INSERT
or DELETE
. And strictly speaking, what you ask for is impossible even for an UPDATE
trigger:
depending on a column
c2
inc
: if that joined column changed ...
A trigger function on table a
only sees a single snapshot of table c
. There is no way to detect any "change" in that table. If you really meant to write:
depending on column
a.a1
: if that changed so that the referenced valuec.c2
is different now ...
.. then there is a way:
Since a BEFORE
trigger is less prone to endless loops and other complications, I demonstrate a BEFORE UPDATE
trigger. (Changing to AFTER
is trivial.):
CREATE OR REPLACE FUNCTION p_upbef()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
IF NEW.a1 <> OLD.a1 THEN -- assuming a1 is defined NOT NULL
IF (SELECT count(DISTINCT c.c2) > 1 -- covers possible NULL in c2 as well
FROM c
WHERE c.c1 IN (NEW.a1, OLD.a1)) THEN
-- do something
END IF;
END IF;
RETURN NEW;
END
$func$;
If a1
can be NULL and you need to track changes from / to NULL as well, you need to do more ...
Trigger:
CREATE TRIGGER upbef
BEFORE UPDATE ON a
FOR EACH ROW EXECUTE FUNCTION p_upbef ();
Since everything depends on a change in a.a1
now (and you don't have other things in the trigger) you can move the outer IF
to the trigger itself (cheaper):
CREATE OR REPLACE FUNCTION p_upbef()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
IF (SELECT count(DISTINCT c.c2) > 1 -- covers NULL as well
FROM c
WHERE c.c1 IN (NEW.a1, OLD.a1)) THEN -- assuming a1 is NOT NULL!
-- do something
END IF;
RETURN NEW;
END
$func$;
Trigger:
CREATE TRIGGER upbef
BEFORE UPDATE OF a1 ON a -- !
FOR EACH ROW EXECUTE FUNCTION p_upbef();
It's not exactly the same, since an UPDATE
involving the column a1
might actually leave the value unchanged , but it's good enough either way for our purpose: to only run the expensive check on c.c2
in relevant cases.
Related: