简体   繁体   中英

Trigger caused by an update statement inside another trigger

Suppose I have the following tables:

CREATE TABLE foo (
  id_foo int NOT NULL,
  import int NOT NULL,
  CONSTRAINT foo_pk PRIMARY KEY(id_foo)
);

CREATE TABLE bar (
  id_bar int NOT NULL,
  id_foo int NOT NULL,
  import int NOT NULL,
  CONSTRAINT bar_pk PRIMARY KEY(id_bar)
);

ALTER TABLE bar ADD CONSTRAINT fk_bar FOREIGN KEY(id_foo) REFERENCES foo(id_foo);

So, the import column of the foo table must equal the sum of the import column in bar table.

CREATE OR REPLACE FUNCTION foo_restriction() RETURNS TRIGGER AS
$$
  BEGIN
    IF (NEW.import != (SELECT COALESCE(SUM(import),0) FROM bar WHERE id_foo = NEW.id_foo)) THEN
      RAISE EXCEPTION 'import column in foo must be equal in bar table.';
    END IF;

    RETURN NEW;
  END;
$$
LANGUAGE plpgsql;

But I want to automatically sum the new import value in bar to the foo reference.

CREATE OR REPLACE FUNCTION bar_insert() RETURNS TRIGGER AS
$$
  BEGIN
    UPDATE foo SET import = import + NEW.import WHERE id_foo = NEW.id_foo;
    RETURN NEW;
  END;
$$
LANGUAGE plpgsql;


CREATE TRIGGER TR_fooRestriction AFTER INSERT OR UPDATE ON foo FOR EACH ROW EXECUTE PROCEDURE foo_restriction();
CREATE TRIGGER TR_barInsert AFTER INSERT ON bar FOR EACH ROW EXECUTE PROCEDURE bar_insert();

Then the following statement should not work:

INSERT INTO foo(id_foo, import) VALUES(1,25); --FAIL. Works ok.

The following instruction insert a correct tuple for foo, with import = 0.

INSERT INTO foo(id_foo, import) VALUES(2,0); --OK.

But, when I'm trying to add new values in the bat table, the database show me 'import column in foo must be equal in bar table.':

INSERT INTO bar(id_bar, id_foo, import) VALUES
(1,2,100),(2,2,160),(3,2,40);

Whats happening here?, both triggers are executed after each new row, so the restriction in foo table should not be activated in the last statement.

How can I do to fix it?

Thanks!

An AFTER INSERT trigger is executed after insert statement is executed, not after each row is inserted. So your trigger should work well in statements inserting a single row, but not with multirow inserts. To test this behaviour run this script in psql:

drop table if exists test;
create table test (id int);

create or replace function test_trigger()
returns trigger language plpgsql as $$
begin
    raise notice '%', (select count(*) from test);
    return null;
end $$;

create trigger test_trigger 
after insert on test 
for each row execute procedure test_trigger();

insert into test values (1), (2), (3);

DROP TABLE
CREATE TABLE
CREATE FUNCTION
CREATE TRIGGER
NOTICE:  3
NOTICE:  3
NOTICE:  3
INSERT 0 3

For the documentation :

When a row-level AFTER trigger is fired, all data changes made by the outer command are already complete, and are visible to the invoked trigger function.


You could update foo with a sum of values from bar in bar_insert() :

    update foo 
    set import = (
        select sum(import) 
        from bar where id_foo = new.id_foo)
    where id_foo = new.id_foo;
    return null;

However, the best solution is a view. If foo only aggregates import , drop the table and create the view instead:

create or replace view foo_view as 
    select id_foo, sum(import) as import
    from bar
    group by id_foo;

If foo contains some other data, remove import from foo and create the view:

create or replace view foo_view as 
    select foo.*, sum(bar.import) as import_total
    from bar
    join foo using(id_foo)
    group by foo.id_foo;

You do not need any triggers, all is done automatically.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM