I created, as shown below:
- A
tablecalledperson - A
tablecalledlog, with 1 row inserted in it. - A
viewcalledmy_v - A
trigger functioncalledmy_func(), whose purpose is to incrementnum.logby1. - A
triggercalledmy_t, that executesmy_func()beforeeach statement(INSERT,UPDATEorDELETE) onmy_v.
CREATE TABLE person (
name VARCHAR(20)
);
CREATE TABLE log (
num INTEGER
);
INSERT INTO log (num) VALUES (0);
CREATE VIEW my_v AS
SELECT * FROM person;
CREATE FUNCTION my_func() RETURNS trigger
AS $$
BEGIN
UPDATE log SET num = num + 1;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER my_t BEFORE INSERT OR UPDATE OR DELETE ON my_v
FOR EACH STATEMENT EXECUTE FUNCTION my_func();
For good measure, I also tested when the trigger my_t executes after each statement
CREATE TRIGGER my_t AFTER INSERT OR UPDATE OR DELETE ON my_v
FOR EACH STATEMENT EXECUTE FUNCTION my_func();
With this setup, the value of num in table log is expected to count how many times my_func() was invoked by the trigger.
When inserting, updating or deleting a row into/from my_v, log.num remains at 0. See below the statements, the last one of which is the check from table log (actually tested after each action but removed from here in order to shorten the question):
postgres=# INSERT INTO my_v (name) VALUES ('John');
INSERT 0 1 /* -> 1 row successfully inserted */
postgres=# UPDATE my_v SET name = 'Tom';
UPDATE 1 /* -> 1 row successfully updated */
postgres=# DELETE FROM my_v;
DELETE 1 /* -> 1 row successfully deleted */
postgres=# SELECT num FROM log;
num
-----
0
(1 row)
So, the trigger with BEFORE or AFTER and INSERT, UPDATE or DELETE on a view doesn't work in PostgreSQL.
Important: an alternative version of the my_t trigger with INSTEAD OF and FOR EACH ROW does work properly:
CREATE TRIGGER my_t INSTEAD OF INSERT OR UPDATE OR DELETE ON my_v
FOR EACH ROW EXECUTE FUNCTION my_func();
How can I create my_t trigger which triggers my_func() before or after INSERT, UPDATE or DELETE operation happens on my_v for each statement?
This is by design, although not obvious it is documented. BEFORE / AFTER triggers on Views is only supported for those views that have an INSTEAD OF trigger.
We can demonstrate this working in a fiddle: https://www.db-fiddle.com/f/Ki2fBnAKC4rH1kMs66UaT/0
There is a flaw in the delete handler, name should be
nullin the end but its not important to this demo.If you run the same test, this time with the INSTEAD OF trigger removed https://www.db-fiddle.com/f/jE6tbJAGTGRqpYKgXW346n/1 then only the trigger on the table fires
nullAlthough this looks like a strange implementation, the purpose behind this is to extend the support for INSERT/UPDATE/DELETE against complex views that can only be defined in INSTEAD OF triggers. The subsystem will call a statement level BEFORE trigger once, before executing the INSTEAD OF trigger for each ROW. At the end of the operation the AFTER statement trigger will be called.
Other RDBMS do not generally support triggers on views. This was not designed to allow
BEFORE/AFTERon any arbitrary view but specifically to execute pre-conditions that can prevent the INSTEAD OF ROW statements from executing at all but also because the order of the ROW operations is indeterminate so it allows an extensibility site to assist you to audit or trace the entire batch instead of you trying to engineer solutions that try to track the difference between first/last ROW inside the FOR EACH ROW.