NULLIF(分母, 0)是唯一既安全又跨库兼容的防除零写法,因其将分母为0转为NULL,使除法结果为NULL而不报错;必须作用于分母本身、顺序正确、第二参数为字面量0,否则失效。

直接用 NULLIF(分母, 0) 包住聚合后的分母,是唯一既安全又跨库兼容的写法;其他方式要么漏掉错误点,要么引入新风险。
为什么聚合后除法才真正危险
聚合函数如 SUM()、COUNT() 本身不报错,它们天然跳过 NULL、忽略空值。但一旦结果参与除法——比如 SUM(revenue) / SUM(cost)——某组算出来 SUM(cost) 等于 0,PostgreSQL/SQL Server/Oracle 就立刻抛出 division by zero,MySQL 在严格模式下也一样。这不是数据问题,是表达式没兜底。
常见错误现象包括:Msg 8134, Level 16, State 1(SQL Server)、ERROR: division by zero(PostgreSQL),整条查询直接中断,一行结果都不返回。
-
HAVING SUM(cost) > 0会直接过滤掉该组,但业务上“零成本订单”可能需要保留并标记为NULL或N/A -
WHERE cost != 0对聚合结果无效——SUM(cost)是标量,不能在WHERE中引用 - 试图在外层加
CASE WHEN SUM(cost) = 0 THEN NULL ELSE ... END:语法报错,因为WHERE/HAVING外不允许在SELECT中对聚合结果做条件判断(除非用子查询或 CTE)
NULLIF 必须这么写,顺序和字面量都不能错
NULLIF 的语义是“相等即返 NULL”,所以它必须作用于**除数本身**,且第二个参数必须是字面量 0(不是 0.0,也不是列名或表达式)。
✅ 正确:SUM(revenue) / NULLIF(SUM(cost), 0)
❌ 错误:NULLIF(SUM(revenue), 0) / SUM(cost) —— 分母没包,白加
❌ 错误:NULLIF(SUM(revenue) / SUM(cost), 0) —— 除法先执行,已经崩了
❌ 错误:NULLIF(SUM(cost), 0.0) —— 浮点精度可能导致比较失效,尤其当 SUM(cost) 是整型时
额外注意:NULLIF(NULL, 0) 返回 NULL(符合预期);但 NULLIF(0, NULL) 返回 0(因为 0 = NULL 判定为 unknown,不满足相等条件)。
加了 NULLIF 之后,结果怎么显示才合理
NULLIF 只解决“不断查询”,不解决“怎么呈现”。业务中 NULL 很少能直接透出——转化率是 NULL,前端该显示 “—”、“0%” 还是 “N/A”?取决于语义。
- 想标“无分母即为 0%”:
COALESCE(SUM(done) * 100.0 / NULLIF(SUM(plan), 0), 0.0) - 想标“异常需人工核查”:
COALESCE(SUM(done) * 100.0 / NULLIF(SUM(plan), 0), -1) - 千万别写成:
SUM(done) * 100.0 / COALESCE(NULLIF(SUM(plan), 0), 0)—— 外层COALESCE把NULL换回0,又回到除零 - 记得乘
100.0而非100:避免整数除法截断(例如 PostgreSQL 中5/2 = 2)
不同数据库的兼容性与边界行为
NULLIF 是 SQL:2003 标准函数,PostgreSQL、SQL Server、Oracle、Snowflake、Trino、SQLite、MySQL 5.7+ 都支持。但要注意:
- BigQuery 不支持
NULLIF,得改用IF(expression = 0, NULL, expression) - Oracle 没有
NULLIF,必须手写CASE WHEN y = 0 THEN NULL ELSE y END - 如果分母是浮点类型(如
DOUBLE),NULLIF(x, 0.0)可能因精度失效;稳妥做法是转成定点数:NULLIF(CAST(x AS DECIMAL), 0) -
COALESCE和NULLIF组合不能“捕获错误”,只是提前让分母失效;所有兜底都必须发生在除法完成之后,括号位置错一个就前功尽弃
最易被忽略的一点:当分母来自条件聚合(如 COUNT(CASE WHEN status='done' THEN 1 END))时,NULLIF 依然有效,但你要确认这个计数逻辑本身是否真能为 0——比如全行都不满足 status='done',结果就是 0,这时 NULLIF 才真正起作用。

















