【问题标题】:Run trigger after transaction, not on each row - PostgreSQL 9.4事务后运行触发器,而不是每一行 - PostgreSQL 9.4
【发布时间】:2016-09-13 14:58:24
【问题描述】:

我有这个触发器来确保所有投票的总和不超过100:

CREATE FUNCTION limit_votes() RETURNS TRIGGER AS $limit_votes$
    DECLARE
        votes_total int;
    BEGIN
        votes_total := NEW.vote + (SELECT SUM(vote) FROM cars WHERE portfolio_id = NEW.portfolio_id AND car != NEW.car);
        IF votes_total > 100 THEN
            RAISE EXCEPTION 'votes_limit_exceeded';
        END IF;
        RETURN NEW;
    END;
$limit_votes$ LANGUAGE plpgsql;
CREATE TRIGGER limit_votes BEFORE INSERT OR UPDATE ON cars FOR EACH ROW EXECUTE PROCEDURE limit_votes();

应该提到“投票”是可以更新的,所以它不仅仅是 INSERT。 这就是我的问题发生的地方,因为一个用户可以更改汽车之间的投票,然后在其他更新减少其他投票之前,触发单行的更新可能会超过 100 的限制。

我希望它有意义。

我希望在 BEGIN ... COMMIT 之后调用触发器,因此我可以在检查一切正常之前更新多行 - 但如果用户尝试更新投票,也会引发异常并且不保存结果超过 100 个。

【问题讨论】:

    标签: postgresql


    【解决方案1】:

    触发器必须是约束和“之后”触发器。然后你可以使用 DEFERRABLE:

    CREATE CONSTRAINT TRIGGER limit_votes
    AFTER INSERT OR UPDATE ON cars
    DEFERRABLE
    FOR EACH ROW EXECUTE PROCEDURE limit_votes();
    

    查看文档:

    http://www.postgresql.org/docs/9.1/static/sql-createtrigger.html

    【讨论】:

    • 更改为“在插入或更新汽车之前创建触发器 limit_votes 可推迟每行执行过程 limit_votes();”在“DEFERRABLE”处或附近出现语法错误而失败。
    • 当它是 AFTER 触发器时,如果更新了“非法”内容会怎样?还会被丢弃吗?
    • @MichaelNielsen 是的,我认为它会回滚整个事务。但请确保 100% 确定。
    • 这只对我有效,方法是添加 INITIALLY DEFERRED: CREATE CONSTRAINT TRIGGER limit_votes AFTER INSERT OR UPDATE ON cars DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE limit_votes();
    • 它需要INITIALLY DEFERRED。来自文档:INITIALLY IMMEDIATE INITIALLY DEFERRED If a constraint is deferrable, this clause specifies the default time to check the constraint. If the constraint is INITIALLY IMMEDIATE, it is checked after each statement. This is the default. If the constraint is INITIALLY DEFERRED, it is checked only at the end of the transaction. The constraint check time can be altered with the SET CONSTRAINTS command.
    【解决方案2】:

    根据https://www.postgresql.org/docs/13/sql-createtrigger.html

    由于这应该是一个可延迟的约束,并且您希望在事务之后触发过程执行,因此您需要以下内容,

    最初推迟

    CREATE CONSTRAINT TRIGGER limit_votes
    AFTER INSERT OR UPDATE ON cars
    DEFERRABLE
    INITIALLY DEFERRED 
    FOR EACH ROW EXECUTE PROCEDURE limit_votes();
    

    最初立即

    如果我们想在每个语句之后触发过程执行。

    CREATE CONSTRAINT TRIGGER limit_votes
    AFTER INSERT OR UPDATE ON cars
    DEFERRABLE
    INITIALLY IMMEDIATE 
    FOR EACH ROW EXECUTE PROCEDURE limit_votes();
    

    【讨论】:

      【解决方案3】:

      虽然现有答案会处理 run trigger after transaction 部分,但该函数仍将针对每一行运行。为了确保它只运行一次,您可以在额外的 triggers 表上添加中间触发器,如下所示:

      -- triggers table for logging and firing a trigger only once
      drop table if exists triggers;
      create table triggers(
          "tx_id" bigint primary key,
          "name" text,
          "exec_count" int default 0
      );
      
      -- the original trigger now fires an insert into the triggers table
      create constraint trigger cars_insert_update
      after insert or update on cars
      deferrable
      initially deferred
      for each row 
      execute function insert_trigger();
      
      -- trigger function to insert a row in the triggers table
      create or replace function insert_trigger()
      returns trigger as $insert_trigger$
      begin
          insert into triggers
          values (txid_current(), tg_name, 0)
          on conflict (tx_id)
          do nothing;
          
          return null;
      end
      $insert_trigger$ language plpgsql;
      
      -- trigger to run the original trigger function
      -- it will only fire once since updates are ignored
      drop trigger if exists triggers_insert on triggers;
      create constraint trigger triggers_insert
      after insert on triggers
      deferrable
      initially deferred
      for each row
      when (new."name" = 'cars_insert_update')
      execute function limit_votes();
      

      【讨论】:

      • 如果你想为每个语句运行触发器,你可以简单地使用for each statement而不是for each row
      • 如果是约束(延迟)触发器则不会。
      猜你喜欢
      • 1970-01-01
      • 2015-07-18
      • 2021-12-02
      • 1970-01-01
      • 2021-09-08
      • 1970-01-01
      • 2012-02-14
      • 1970-01-01
      • 2011-06-16
      相关资源
      最近更新 更多