COALESCE(SUM(done) * 100.0 / NULLIF(SUM(plan), 0), 0.0)是唯一安全写法,因NULLIF必须严密封装分母防除零,外层COALESCE才可兜底显示;写反顺序或用0.0/字符串将导致失效或报错。

COALESCE + NULLIF 必须这样嵌套才防除零
直接写 COALESCE(SUM(done) / SUM(plan), 0) 会报 ERROR: division by zero,因为除法在 COALESCE 外层执行,错误已发生。真正安全的写法是把 NULLIF 严密封装在分母位置:SUM(done) * 100.0 / NULLIF(SUM(plan), 0)。这一步不是可选优化,是防止查询中断的硬性要求。
-
NULLIF(SUM(plan), 0)在SUM(plan) = 0时返回NULL,使整条除法结果为NULL(数据库对任何X / NULL都返回NULL,不报错) -
NULLIF(SUM(plan), 0.0)是错的——0.0是NUMERIC,而SUM(plan)可能是INTEGER,类型不一致导致比较失效,NULLIF不触发 -
NULLIF对NULL输入原样返回NULL(NULL = 0判定为UNKNOWN),所以它天然兼容聚合结果本身为NULL的情况
转化率结果怎么显示,取决于业务语义
NULLIF 只解决“不断查”,不决定“怎么展”。前端看到 NULL 往往不能直接透出,得按场景兜底:
- 想标“无分母即为 0%”:用
COALESCE(SUM(done) * 100.0 / NULLIF(SUM(plan), 0), 0.0) - 想标“数据异常需人工核查”:用
COALESCE(SUM(done) * 100.0 / NULLIF(SUM(plan), 0), -1),后端或 BI 工具据此加标记 - 千万别写成
SUM(done) * 100.0 / COALESCE(NULLIF(SUM(plan), 0), 0)——外层COALESCE把NULL换回0,又回到除零 - 记得乘
100.0而非100:避免整数除法截断(例如3/5在 PostgreSQL 中得0,而3.0/5得0.6)
WHERE 或 HAVING 里不能替代 NULLIF
有人试图用 HAVING SUM(plan) > 0 过滤掉零分母组,但这会直接丢弃该组数据;业务上“零计划量但有完成量”的记录可能需要保留并标记为异常。同样,WHERE plan != 0 对聚合列无效,语法就通不过。这些写法要么丢失数据,要么语法报错,根本绕不开 NULLIF 的位置要求。
-
WHERE作用于原始行,不能引用SUM(plan) -
HAVING虽能过滤分组,但属于“删数据”而非“标异常”,和转化率计算的语义不匹配 - 如果真要区分处理,得用子查询或 CTE 先算出分母,再在外层
CASE WHEN分支,但比NULLIF冗长且易错
类型不一致会静默失败,必须显式对齐
COALESCE 所有参数类型必须兼容,否则 PostgreSQL 直接报错:ERROR: COALESCE types text and integer cannot be matched。常见于混合使用字符串默认值和数值计算:
- 错:
COALESCE(SUM(done) * 100.0 / NULLIF(SUM(plan), 0), 'N/A')—— 数值 vs 字符串,类型冲突 - 对:
COALESCE(SUM(done) * 100.0 / NULLIF(SUM(plan), 0), -1.0),全部保持NUMERIC - 如果非要返回字符串,得统一转类型:
COALESCE(SUM(done) * 100.0 / NULLIF(SUM(plan), 0), -1.0)::TEXT
最常被忽略的一点:NULLIF 只认字面量 0,不接受变量、表达式或浮点 0.0;而 COALESCE 的兜底值类型必须和前面整个表达式推导出的类型一致——这两处类型和字面量的硬约束,一旦写错,不是结果不准,就是查询直接失败。










