WHEN子句只能用于行级触发器(FOR EACH ROW),不可用于语句级触发器;它仅支持静态布尔表达式,禁止函数调用、子查询及副作用操作,且NULL参与比较时整条件判false。

WHEN子句不能代替函数内IF判断,它必须写在触发器定义里、且只对行级触发器生效——否则条件根本不会被评估。
WHEN子句只能用在FOR EACH ROW触发器中
语句级触发器(FOR EACH STATEMENT)无法访问NEW或OLD,所以WHEN (NEW.status = 'done')会直接报错:ERROR: transition tables not allowed in statement-level triggers。这不是语法疏忽,而是设计限制。
- 正确写法必须是:
FOR EACH ROW WHEN (OLD.status IS DISTINCT FROM NEW.status) - 误写成
FOR EACH STATEMENT WHEN (...)→ 立刻失败,不提示具体原因,只报语法错误 - 若你本意是“只对特定用户类型触发”,
WHEN (NEW.role IN ('admin', 'editor'))可行,但NEW.role为NULL时整条条件判为false,不会进触发器
WHEN表达式禁止函数调用和子查询
它不是运行时求值的逻辑块,而是由优化器静态分析的布尔表达式,类似CHECK约束。任何可能产生副作用或依赖外部状态的操作都被禁止。
- 允许:
NEW.amount > 1000、OLD.updated_at 、<code>NEW.email ~* '^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$'(正则字面量) - 禁止:
lower(NEW.email)、(SELECT active FROM users WHERE id = NEW.user_id)、CURRENT_USER = 'admin'(即使CURRENT_USER是稳定函数,也建议避免) - 若逻辑必须查表,得退回到触发器函数内部用
IF,但代价是每行都调用一次函数——哪怕最终RETURN NULL,开销已发生
BEFORE vs AFTER中WHEN的行为差异很小,但关键点不能错
WHEN本身不区分BEFORE还是AFTER,它只决定“这一行要不要进触发流程”。但后续行为天差地别:
-
BEFORE ROW WHEN (...):条件满足时,函数可修改NEW字段(如自动填充updated_at),也可RETURN NULL取消该行操作(慎用) -
AFTER ROW WHEN (...):DML已提交,NEW改了也白改;只能做日志、发消息、更新统计表等副作用 - 常见陷阱:想用
BEFORE + WHEN阻止非法状态变更,却忘了RETURN NULL后整行被跳过——这和业务上“拒绝更新但返回错误”不是一回事
PostgreSQL 15起WHEN支持短路优化,但字段比较仍需注意NULL语义
OLD.status != NEW.status在任一端为NULL时结果为UNKNOWN,整个条件失效。这是最容易被忽略的逻辑断点。
- 正确写法应显式处理:
OLD.status IS DISTINCT FROM NEW.status(IS DISTINCT FROM把NULL当相等值比较) - 不要依赖
COALESCE(OLD.status, '') != COALESCE(NEW.status, ''),字符串转换可能掩盖真实语义 - 批量更新时,
WHEN能砍掉70%以上无效调用——前提是条件足够早地筛掉无关行,而不是让函数内部再判断一遍
真正难的不是写对语法,而是在WHEN里把业务意图转成无副作用、无NULL陷阱、不越权查表的纯布尔表达式。一旦需要JOIN或EXISTS,说明它已经不该放在WHEN里了。

















