postgresql的when子句仅适用于for each row行级触发器,用于执行前过滤满足条件的行;不支持函数调用、子查询或null敏感比较,需显式处理null;before中可修改new并取消操作,after中仅能执行副作用。

PostgreSQL 的 WHEN 子句能真正实现“按需触发”,不是靠函数内部 IF 判断绕弯子,而是让数据库在执行前就跳过不满足条件的行——更高效、更安全、也更容易排查。
WHEN 子句必须用在行级触发器中
语句级触发器(FOR EACH STATEMENT)无法访问 NEW 或 OLD,因此 WHEN 里写 NEW.status = 'completed' 会直接报错:ERROR: transition tables not allowed in statement-level triggers。只有 FOR EACH ROW 触发器才能用 WHEN 做行级过滤。
- 误写成
FOR EACH STATEMENT WHEN (NEW.status = ...)→ 语法错误,立刻失败 - 正确写法必须是:
FOR EACH ROW WHEN (OLD.status 'completed' AND NEW.status = 'completed') - 如果你本意是“只对某几类用户触发”,
WHEN里可以直接引用NEW.customer_type,但前提是该列存在且非 NULL(NULL 比较结果为 UNKNOWN,整个条件判为 false)
WHEN 条件里不能调用函数或子查询
WHEN 子句只接受纯布尔表达式,不支持函数调用(如 lower(NEW.email))、子查询(如 (SELECT COUNT(*) FROM logs WHERE user_id = NEW.id) > 0)或任何可能产生副作用的操作。它在触发器执行前由优化器静态评估,类似 CHECK 约束的表达式限制。
- 允许:
NEW.amount > 1000、OLD.status IS DISTINCT FROM NEW.status、NEW.created_at - 不允许:
my_validation_func(NEW)、(SELECT active FROM users WHERE id = NEW.user_id)、CURRENT_USER = 'admin'(虽然CURRENT_USER是函数,但 PostgreSQL 允许少数几个稳定函数,不过仍建议避免) - 若逻辑复杂必须查表或计算,得退回到触发器函数内部用
IF,但要注意:这会多执行一次函数调用,哪怕最终RETURN NULL,开销已发生
BEFORE 和 AFTER 触发器对 WHEN 的影响很小,但有关键区别
WHEN 本身不区分 BEFORE 还是 AFTER,它只决定“这一行要不要进触发流程”。但后续行为差异很大:
-
BEFORE ROW WHEN (...):条件满足时,触发器函数可修改NEW(比如自动填充updated_at),然后 DML 继续执行;若函数RETURN NULL,整行操作被取消(慎用) -
AFTER ROW WHEN (...):条件满足时,DML 已完成,触发器只能做副作用操作(如写日志、更新统计表);此时改NEW无效,因为数据已落盘 - 常见陷阱:想用
BEFORE+WHEN阻止非法状态变更,却忘了WHEN不等于校验——它只是前置开关;真正的校验仍需在函数里RAISE EXCEPTION
WHEN 条件中的 NULL 处理容易被忽略
NULL = 'x' 结果是 UNKNOWN,不是 TRUE 或 FALSE,而 WHEN 只有在表达式明确为 TRUE 时才触发。这意味着 WHEN (NEW.category = 'vip') 在 category 为 NULL 时不会触发,哪怕你本意是“所有非普通用户”。
- 安全写法:
WHEN (NEW.category IS NOT NULL AND NEW.category = 'vip') - 想包含 NULL?用
IS NOT DISTINCT FROM:例如WHEN (OLD.status IS NOT DISTINCT FROM 'pending')能同时匹配'pending'和NULL - 别依赖
COALESCE(NEW.category, 'normal') = 'vip'——COALESCE允许,但语义易混淆,且掩盖了原始 NULL 状态
真正难的不是写对 WHEN 语法,而是想清楚:这个条件是否应该由数据库强制执行(用 WHEN + BEFORE + RAISE),还是仅作轻量筛选(WHEN + AFTER 更新统计)。前者关乎数据一致性,后者只是性能优化。










