新记录的值似乎被保留为 PostgreSQL 触发器函数中两个 if/elseif 块之间的旧记录

A value of a NEW record seems to be kept as the OLD record between two if/elseif blocks in a PostgreSQL trigger function

我有一个枚举 ('not created', 'in creation', 'created', 'updated', 'rejected') 和 PostgreSQL 中的触发器,它包含一些用于更改 INSERT 或 UPDATE 记录状态的逻辑:

CREATE OR REPLACE FUNCTION set_status()
    RETURNS trigger AS
    $BODY$
    BEGIN
        IF TG_OP ILIKE 'UPDATE' THEN -- this line was added after Pavel Stehule's answer
            IF (NEW.field1 IS NULL) THEN
                NEW.status = 'not created';
            ELSEIF (NEW.field1 IS NOT NULL) THEN
                IF OLD.status IN ('not created')
                THEN
                    NEW.status = 'created'; -- point 1
                ELSEIF OLD.status IN ('created', 'updated', 'rejected') -- point 2
                AND NEW.status NOT IN ('not created')
                THEN
                    NEW.status = 'updated';
                ELSEIF NEW.status NOT IN ('not created', 'created', 'updated', 'rejected')
                THEN
                    NEW.status = 'in creation';
                ELSE
                    NEW.status = 'not created';
                END IF;
            END IF;
        END IF; -- this line was added after Pavel Stehule's answer
        RETURN NEW;
    END
    $BODY$
LANGUAGE 'plpgsql';

CREATE TRIGGER update_status_biut
    BEFORE INSERT OR UPDATE
    ON table
    FOR EACH ROW
    EXECUTE PROCEDURE set_status();

一切正常,除了当状态以 'not created' 值开始(这是新记录的默认值)时,它会在 UPDATE 或 INSERT 上直接切换到 'updated' 它应该保持的位置'created' 第一次处理记录。

对我来说,这听起来像是我放置“--点 1”的地方,NEW.status 在这里取值 'created',这是正确的,但在下一个 ELSEIF 块,它重新使用该值作为 'OLD.status' (?!),从而验证条件,使其切换到 'updated'.

我还通过尝试从“-- 点 2”的列表中删除 'created' 值来验证这一点。在这种情况下,在 UPDATE/INSERT.

之后,状态会保持在 'created'

如何避免这种奇怪的行为并在 if 块中保留已满足的第一个值('created' 在这种情况下)并 'pass' (以下)其他值?

版本信息(Dockerized PostgreSQL):

# psql --version
psql (PostgreSQL) 13.3 (Debian 13.3-1.pgdg100+1)

编辑:我找到了麻烦的根源...

来源:https://www.postgresql.org/docs/9.1/rules-update.html

而且我真的不知道如何在两个触发器的情况下正确处理这些伪关系,它们都修改了两对不同的列,但需要对更多的传入属性进行一些检查。触发器 A 的强制属性是整个 table 的一个子集,触发器 B 也一样,有少量重叠。所有其他功能都不是从 Python 客户端应用程序传递的,因此它们实际上采用数据库中的 OLD 值。如果这个值需要为空才能触发触发器B,一旦它们被填充,它就不能再触发了,即使它需要!

NEWOLD 变量的行为取决于使用的 PostgreSQL 版本。 NEWINSERTUPDATE 事件的明确定义。 OLDUPDATEDELETE 有很好的定义。当您在 INSERT 事件中使用 OLD 时,您会在较旧的 Postgreses 上遇到异常,今天您将得到 NULL.

你的设计不好。对于这种情况,您应该使用隐式变量 TG_OP,它包含字符串:INSERTUPDATEDELETE,然后您可以设置正确的状态。

您可能不了解基本的 SQL UPDATE 机制。 The manual:

columns not explicitly modified retain their previous values.

因此触发器函数中 NEW 的所有字段尚未明确更新或未被另一个更早的触发器保留其原始值 - 与它们的 OLD 对应部分相同。

另外,您的解读不准确:

but on the next ELSEIF block, it re-uses that value as the OLD.status (?!)

事情不是这样的。在IF语句中只能执行one分支。之后,控制跳转到 END IFThe manual:

The IF conditions are tested successively until the first one that is true is found. Then the associated statement(s) are executed, after which control passes to the next statement after END IF. (Any subsequent IF conditions are not tested.) If none of the IF conditions is true, then the ELSE block (if any) is executed.

除此之外,由于您的(更新的)触发器无论如何对 INSERT 情况没有任何作用,因此简化:

CREATE OR REPLACE FUNCTION tbl_set_status()
  RETURNS trigger
  LANGUAGE plpgsql AS
$func$
BEGIN
   IF NEW.field1 IS NULL THEN
      NEW.status = 'not created';
   ELSE
      IF OLD.status = 'not created' THEN
         NEW.status = 'created'; -- point 1
      ELSIF OLD.status IN ('created', 'updated', 'rejected') -- point 2
      AND NEW.status <> 'not created' THEN
         NEW.status = 'updated';
      ELSIF NEW.status NOT IN ('not created', 'created', 'updated', 'rejected') THEN
         NEW.status = 'in creation';
      ELSE  --  redundant catch-all
         NEW.status = 'not created';
      END IF;
   END IF;
   
   RETURN NEW;
END
$func$;

CREATE TRIGGER update_status_biut
BEFORE UPDATE ON tbl
FOR EACH ROW EXECUTE FUNCTION tbl_set_status();

如果您需要 INSERT 触发器来处理其他内容,请考虑使用单独的触发器(和函数)。

旁白:您从 'rejected' 自动升级到 'updated' 似乎很奇怪。

我遇到的事情比我在第一条消息中实际暴露的要复杂得多。抱歉。

我没有太多时间来展开解决方案,但是 `OF column_name[ ...] 的用法是它的主要部分:

资料来源:https://www.postgresql.org/docs/13/sql-createtrigger.html

通过使用此选项,触发器仅在更新指定列时触发,从而避免了“伪像”(好吧,它并不是真正的伪像,但当您第一次遇到这种行为时它会非常奇怪) NEW.not_specified_field(在部分 UPDATE 请求期间)的 NEW.not_specified_field(在部分 UPDATE 请求期间)瞬间(即 'on-the-fly',即在触发函数内部使用之前)从数据库(您如果有必要检查一个空的传入值,可能不需要!)。

这样我就可以删除我在触发器函数中所做的杂乱检查。现在干净多了。

这个 post 也帮助我弄明白了: