我有一个带有更新前触发器的表,它可以通过更新同一行来触发自身,但更新语句的结果不是该触发器返回的最后一个值。我不明白为什么以及如何工作。这个问题在例子上更容易理解:
BEGIN;
SET client_min_messages = 'notice';
CREATE TABLE public.test (
id INTEGER,
status INTEGER,
value numeric
);
CREATE SEQUENCE test_seq;
CREATE OR REPLACE FUNCTION public.test_tr() RETURNS trigger AS $body$
DECLARE
seq integer;
BEGIN
seq := nextval('test_seq');
RAISE NOTICE 'Started: %', seq;
IF new.status != old.status THEN
UPDATE test SET value = new.value WHERE id = new.id;
END IF;
RAISE NOTICE 'Finished: %, RETURNED NEW: %', seq, new;
RETURN new;
END
$body$ LANGUAGE 'plpgsql';
CREATE TRIGGER test_tr BEFORE UPDATE ON test FOR EACH ROW EXECUTE PROCEDURE test_tr();
INSERT INTO test (id, status, value) VALUES (1, 1, 1);
UPDATE test SET status = 2, value = 2 WHERE id = 1;
SELECT * FROM test;
ROLLBACK;
产生以下结果:
NOTICE: Started: 1
NOTICE: Started: 2
NOTICE: Finished: 2, RETURNED NEW: (1,1,2)
NOTICE: Finished: 1, RETURNED NEW: (1,2,2)
UPDATE 0
id | status | value
----+--------+-------
1 | 1 | 2
如您所见,最后返回的 new 是 (1,2,2) (status = 2, value = 2),但最后一个 SELECT 返回“status”= 1 和“value”的行"= 2. 为什么 status = "1"?为什么不是“2”?
最佳答案
当 BEFORE UPDATE
触发器在同一行调用 UPDATE
时,手册似乎没有定义任何特定行为。
凭直觉,我们可能期望 NEW
的最后一个值进入该行,但正如结果所表明的那样,事实并非如此。
我建议正在发生的事情:
触发器发出更新 #2。此时该行仍具有其初始
(1,1,1)
值(value)。触发器再次启动更新 #2,但它只返回
NEW
,因此它不会更改任何内容。UPDATE #2 完成后,该行的新版本变为当前版本,内容为
(1,1,2)
。当 UPDATE #1 完成时,引擎应该写入
NEW=(1,2,2)
,但随后它注意到它正在处理的行的版本已经取代。因此它丢弃了它的NEW
记录并保留它。
这与报告 UPDATE 0
的 psql 一致:UPDATE #1 实际上没有更新任何行。
关于postgresql - PostgreSQL 中的递归 BEFORE UPDATE 触发器不使用最后返回的新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21606447/