coalesce是标准sql函数,返回首个非null值,跨数据库兼容;ifnull仅mysql支持、仅两参数、类型处理受限且不兼容迁移。

触发器里直接用 NEW.field = OLD.field 会丢失原始值
很多人在写 BEFORE UPDATE 触发器时,想“保持某字段不变”就写 NEW.status = OLD.status,但若 OLD.status 本身是 NULL,而客户端传入的 NEW.status 是空字符串或 0,这一赋值反而把有意义的值覆盖成了 NULL。这不是 bug,是 SQL 三值逻辑(TRUE/FALSE/UNKNOWN)的自然结果:赋值语句不检查逻辑含义,只机械执行。
实操建议:
- 别默认“没传就是该保留”,先明确业务语义:是“未提供”还是“显式清空”?
- 对可能被
NULL覆盖的关键字段(如状态、时间戳、金额),必须加判断逻辑 - 用
COALESCE(NEW.field, OLD.field)替代裸赋值,前提是确认OLD.field类型安全且非NULL风险字段
COALESCE 在触发器中不是万能兜底,类型和确定性必须提前验证
COALESCE 看似简单,但在触发器上下文中容易踩两个硬坑:类型冲突和非确定性。比如 COALESCE(NEW.updated_at, NOW()) 在 SQL Server 索引视图中会直接报错,因为 NOW()(或 GETDATE())是非确定性函数;又比如 COALESCE(NEW.price, 'N/A') 在 PostgreSQL 中会因类型不兼容拒绝创建触发器。
实操建议:
- 所有参数必须同类型或可隐式转为统一类型——数值列优先用
0.0而非0,避免整型/浮点精度突变 - 禁止在参数中使用运行时函数:
NOW()、NEWID()、USER()等一律替换成常量或触发器外预计算值 - 用
SELECT pg_typeof(COALESCE(...))(PostgreSQL)或sp_help 'your_view'(SQL Server)验证返回类型是否符合预期
WHERE 条件里误用 COALESCE 会导致逻辑错位和索引失效
有人想在触发器里写 IF COALESCE(NEW.category, '') = '' THEN ... 来捕获“空分类”,这看似合理,但实际漏掉了两种情况:一是 NEW.category 是空字符串 ''(被 COALESCE 拦截后无法区分),二是 NEW.category IS NULL 和 NEW.category = '' 在业务上本应不同处理,却被强行归为一类。
更严重的是性能问题:COALESCE(NEW.category, '') = '' 是表达式计算,数据库无法利用 category 列上的索引,哪怕只是做行级判断。
实操建议:
- 条件分支优先用原生判空:
IF NEW.category IS NULL OR NEW.category = '' THEN - 真要合并语义,应在应用层或存储过程里做标准化,而不是在触发器里用
COALESCE掩盖差异 - 涉及索引字段的判断,永远避免包裹函数——包括
COALESCE、TRIM、UPPER等
聚合或运算前不逐字段 COALESCE,结果必然不可靠
触发器里常见写法:NEW.total = NEW.price * NEW.quantity + NEW.tax。只要其中任意一列是 NULL,整个表达式结果就是 NULL,且不会报错,只会静默中断后续逻辑。这不是触发器没运行,而是 SQL 的“NULL 传染性”在起作用。
实操建议:
- 每个参与运算的字段都单独包裹:
COALESCE(NEW.price, 0) * COALESCE(NEW.quantity, 1) + COALESCE(NEW.tax, 0) - 字符串拼接同理:
CONCAT(COALESCE(NEW.first_name, ''), ' ', COALESCE(NEW.last_name, '')) - 警惕聚合函数位置:
SUM(COALESCE(sales, 0))是逐行补零再求和;COALESCE(SUM(sales), 0)是整列全NULL才补一次零,二者语义完全不同
最易被忽略的一点:触发器中 COALESCE 的行为依赖于数据库引擎对标准 SQL 的实现程度。MySQL 5.7 对多参数类型推导比 PostgreSQL 15 更宽松,但代价是运行时才暴露类型错误;SQLite 的 COALESCE 允许混合类型却静默转成文本,导致下游数值计算出错。跨库迁移前,务必用真实数据集跑一遍边界 case。










