WHEN子句仅适用于FOR EACH ROW触发器,不支持语句级触发器、函数调用、子查询,必须配合UPDATE OF使用,并用IS DISTINCT FROM安全比较NULL。

WHEN子句只在行级触发器中生效
如果你写了 AFTER UPDATE ON users FOR EACH STATEMENT,再加 WHEN (OLD.email != NEW.email),PostgreSQL 会直接报错:ERROR: WHEN clause not allowed on statement-level triggers。WHEN 只能用于 FOR EACH ROW 触发器,这是硬性限制,不是文档疏漏。
原因在于语句级触发器不暴露 OLD 和 NEW 记录——它根本不知道某一行具体变了什么,只感知“这个 UPDATE 语句执行了”。所以所有字段级条件判断必须落在行级上下文中。
- 想监控 email 变更?必须用
FOR EACH ROW - 想统计整条 UPDATE 影响了多少行?用
FOR EACH STATEMENT,但别加 WHEN - 混合需求(比如“只要 email 变了就记日志,同时整条语句结束时发通知”)?得建两个触发器:一个行级带 WHEN,一个语句级无条件
WHEN 表达式里不能调用函数或子查询
WHEN (email_changed_func(OLD.email, NEW.email)) 看起来简洁,但 PostgreSQL 不允许。WHEN 子句只接受纯布尔表达式,不支持函数调用、SELECT、CASE 或任何可能产生副作用或需执行计划的操作。
常见错误现象:执行 CREATE TRIGGER 时报 ERROR: cannot use subquery in WHEN clause 或类似提示。
- 正确写法:
WHEN (OLD.email IS DISTINCT FROM NEW.email)(推荐,自动处理 NULL) - 也行:
WHEN (OLD.email != NEW.email OR OLD.email IS NULL != NEW.email IS NULL)(显式判空,啰嗦但明确) - 禁止:
WHEN (my_custom_compare(OLD.email, NEW.email))或WHEN ((SELECT count(*) FROM blacklist WHERE domain = split_part(NEW.email, '@', 2)) > 0)
UPDATE OF column_name 和 WHEN 的配合逻辑容易混淆
UPDATE OF email 是触发前提,WHEN (OLD.email != NEW.email) 是触发后是否执行函数的二次筛选。两者不是等价替代关系,而是先后关系。
举个例子:执行 UPDATE users SET email = 'a@b.com', name = 'Alice' WHERE id = 1:
- 如果触发器定义为
UPDATE OF email→ 满足触发前提,进入下一步 - 若再加
WHEN (OLD.email != NEW.email)→ 实际比较值,若新旧 email 相同(比如 UPDATE 语句里 email 被设成和原来一样),则跳过函数执行 - 反过来,如果只写
WHEN不写UPDATE OF,哪怕只改了name,只要满足 WHEN 条件(比如你误写成WHEN (OLD.name IS NOT NULL)),也会触发——这通常不是你想要的
所以关键判断点是:先用 UPDATE OF 缩小事件范围,再用 WHEN 在该范围内做精准过滤。
NULL 值比较必须用 IS DISTINCT FROM
OLD.email != NEW.email 在任一字段为 NULL 时永远返回 NULL(即 false),导致触发器漏判。这是最常踩的坑,尤其在业务表字段允许 NULL 时。
PostgreSQL 中 NULL = NULL 返回 NULL,NULL != 'x' 也返回 NULL,而 WHEN 子句只在表达式结果为 TRUE 时执行,NULL 和 FALSE 都跳过。
- 安全写法:
WHEN (OLD.email IS DISTINCT FROM NEW.email)—— 它把 NULL 当作一个确定值来比,NULL IS DISTINCT FROM NULL是FALSE,NULL IS DISTINCT FROM 'a@b.com'是TRUE - 等效但冗长:
WHEN (OLD.email != NEW.email OR OLD.email IS NULL AND NEW.email IS NOT NULL OR OLD.email IS NOT NULL AND NEW.email IS NULL) - 别用:
!=、、=直接比含 NULL 的字段
WHEN 子句看着简单,但它的生效边界(仅限行级)、表达式限制(无函数/子查询)、与 UPDATE OF 的协作顺序、以及 NULL 处理这四点,任意一个出错都会让触发逻辑静默失效——而这种问题在线上往往表现为“日志没写”“校验没拦住”,排查起来特别费时间。










