【问题标题】:Joins around if statement for PostgreSQL trigger function加入 PostgreSQL 触发器函数的 if 语句
【发布时间】:2016-12-20 01:48:39
【问题描述】:

我有一个表 a,其中有 3 个触发器,每当插入、更新或删除 a 中的行时,它们会插入、更新或删除 b 中的相应行。所有 3 个触发器都使用相同的触发器函数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 也有一个外键 a1c (主键 c1),我想改变 p 使其进入 IF/@987654332 @ 分支也取决于c 中的c2 列:如果该连接列发生更改,请输入INSERTUPDATE 分支;如果保持不变,请输入 UPDATE 分支。实际上,是这样的:

  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;

oldCnewC 将由类似于这些的连接产生(使用近似语法):

SELECT oldC.* FROM a, c AS oldC WHERE OLD.a1 = c.c1;
SELECT newC.* FROM a, c AS newC WHERE NEW.a1 = c.c1;

因此实际上需要在IF 语句之外进行两个连接,这将允许它引用oldCnewC(或类似的东西)。这可能吗?p 的更改版本看起来如何(使用正确的 PostgreSQL 语法)?

【问题讨论】:

    标签: postgresql join triggers plpgsql database-trigger


    【解决方案1】:

    首先,在DELETE 的情况下没有NEW,所以RETURN NEW; 没有意义并且会引发异常。无论如何,您为AFTER 触发器返回的什么 都无关紧要。还不如RETURN NULL;

    接下来,如果是INSERT,您也不能引用OLD。您必须在之前检查引用OLDNEW 的操作类型。

    你只需要一个 single 触发器,就像你现在拥有的那样:

    CREATE TRIGGER a_i_u_d   -- *one* trigger
    AFTER INSERT OR UPDATE OR DELETE ON a
    FOR EACH ROW EXECUTE PROCEDURE p ();
    

    然而,我建议为INSERTUPDATEDELETE 使用单独的触发函数以避免并发症。然后你需要三个独立的触发器,每个触发器调用各自的触发器函数。

    您要添加的案例会影响UPDATE。没有什么可以像你用INSERTDELETE 描述的那样“改变”。

    严格来说,即使是UPDATE 触发器,您所要求的也是不可能

    取决于c 中的c2 列:如果该连接列发生更改...

    a 的触发器函数只能看到表c单个 快照。无法检测到该表中的任何“更改”。 如果你真的想写:

    取决于a.a1 列:如果改变了,那么现在引用的值c.c2 不同了...

    ..那么有办法:

    由于BEFORE 触发器不太容易出现无限循环和其他并发症,因此我演示了BEFORE UPDATE 触发器。 (更改为AFTER 很简单。):

    CREATE OR REPLACE FUNCTION p_upbef()
      RETURNS trigger 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$  LANGUAGE plpgsql;
    

    如果a1 可以为 NULL,并且您还需要跟踪从 / 到 NULL 的更改,那么您需要做更多...

    触发器:

    CREATE TRIGGER upbef
    BEFORE UPDATE ON a
    FOR EACH ROW EXECUTE PROCEDURE p_upbef ();
    

    由于现在一切都取决于 a.a1 的更改(并且触发器中没有其他内容),您可以将外部 IF 移动到触发器本身(更便宜):

    CREATE OR REPLACE FUNCTION p_upbef()
      RETURNS trigger 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$  LANGUAGE plpgsql;
    

    触发器:

    CREATE TRIGGER upbef
    BEFORE UPDATE OF a1 ON a
    FOR EACH ROW EXECUTE PROCEDURE p_upbef();

    这并不完全相同,因为UPDATE 涉及a1实际上可能使值保持不变,但对于我们的目的而言,无论哪种方式都足够好:仅在相关情况下对 c.c2 运行昂贵的检查

    相关:

    【讨论】:

    • +1 假设我必须在c 上放置一个/另一个触发器以支持第一种解释是否正确(“取决于 c 中的 c2 列:如果加入的列改变了” )?
    • @Drux:是的,要监控c.c2 的变化,您需要在c 上触发。小心不要使用太多触发器创建无限循环。有时,使用数据修改 CTE 的组合查询是一个不错的选择。 Example.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-15
    • 2022-11-07
    • 2012-04-13
    相关资源
    最近更新 更多