连接 PostgreSQL 触发器函数的 if 语句

Dru*_*rux 2 postgresql triggers join plpgsql database-trigger

我有一个a包含 3 个触发器的表,每当插入、更新或删除b行时,它们a都会插入、更新或删除相应的行。所有 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 ();
Run Code Online (Sandbox Code Playgroud)

a还有一个外键a1进入c(带有主键c1),我想p以这样的方式进行更改,使其进入IF/ELSIF分支也取决于c2中的列c:如果该连接列发生更改,请输入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;
Run Code Online (Sandbox Code Playgroud)

其中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;
Run Code Online (Sandbox Code Playgroud)

因此,实际上需要的是IF语句外部的两个连接,这将允许它引用oldCand newC(或类似的东西)。这可能吗?修改后的版本会如何p(使用正确的 PostgreSQL 语法)?

Erw*_*ter 6

首先,没有NEWa 的情况DELETE,所以RETURN NEW;没有意义。这会在 Postgres 11 之前引发异常,该异常已更改

  • 在 PL/pgSQL 触发器函数中,OLDNEW变量现在在未分配时读取为 NULL (Tom Lane)

以前,对这些变量的引用可以被解析但不能被执行。

无论如何,你返回什么触发器并不重要AFTER。也可能是RETURN NULL;

OLD的情况下也没有INSERT

您只需要一个触发器,就像现在一样:

CREATE TRIGGER a_i_u_d   -- *one* trigger
AFTER INSERT OR UPDATE OR DELETE ON a
FOR EACH ROW EXECUTE FUNCTION p ();
Run Code Online (Sandbox Code Playgroud)

不过,我建议为、和单独触发函数以避免复杂化。然后您需要三个单独的触发器,每个触发器调用其各自的触发器函数。INSERTUPDATEDELETE

您要添加的案例只能影响UPDATE. 没有什么可以像您用INSERT或描述的那样“改变” DELETE。严格来说,即使对于触发器,您所要求的也是不可能的UPDATE

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

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

取决于列a.a1:如果发生变化,则引用值c.c2现在不同......

..那么有一个方法:

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

CREATE TRIGGER a_i_u_d   -- *one* trigger
AFTER INSERT OR UPDATE OR DELETE ON a
FOR EACH ROW EXECUTE FUNCTION p ();
Run Code Online (Sandbox Code Playgroud)

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

扳机:

CREATE TRIGGER upbef
BEFORE UPDATE ON a
FOR EACH ROW EXECUTE FUNCTION p_upbef ();
Run Code Online (Sandbox Code Playgroud)

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

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$;
Run Code Online (Sandbox Code Playgroud)

扳机:

CREATE TRIGGER upbef
BEFORE UPDATE OF a1 ON a  -- !
FOR EACH ROW EXECUTE FUNCTION p_upbef();
Run Code Online (Sandbox Code Playgroud)

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

有关的: