聚合函数跳过NULL是设计使然,因NULL代表未知而非零;SUM/AVG/MAX/MIN忽略NULL,全NULL时返回NULL;COUNT(col)统计非NULL行,COUNT(*)统计所有行;COALESCE位置错误易致业务逻辑错误。

聚合函数遇到NULL不是bug,是设计如此;不干预就跳过,但跳过之后的结果可能和你业务想表达的完全相反。
为什么SUM/AVG/MAX/MIN都“看不见”NULL
因为NULL在SQL里代表“未知”,不是0、不是空字符串、也不是false。聚合函数拒绝替你做主观假设——比如AVG(age)碰到NULL,数据库不会猜这个人是18岁还是80岁,直接剔除这行参与计算。
-
SUM([100, NULL, 200])返回300,不是300(别被数字骗了,它真没加那个NULL) - 整列全是NULL时,
SUM(col)和AVG(col)都返回NULL,不是0;下游应用调用getInt()会直接抛异常 -
COUNT(col)统计非NULL行数,COUNT(*)才统计所有行;差值就是该列NULL的数量
SUM(COALESCE(col, 0)) 和 COALESCE(SUM(col), 0) 别写反
这两个看着像,语义天差地别,换错一个就导致财务或库存报表出错。
-
SUM(COALESCE(sales, 0)):先把每行sales里的NULL补成0,再加总 → 适合“缺记录就当0件”的场景,比如商品库存汇总 -
COALESCE(SUM(sales), 0):先加总,发现结果为NULL(比如整组都没销售),再兜底成0 → 适合“空部门显示0元”的报表需求 - 错误示范:
AVG(COALESCE(salary, 0))把[NULL, 10000]算成5000,但真实情况可能是两人均未发薪,不该拉低平均值
LEFT JOIN后字段为NULL,聚合前必须清洗
JOIN没匹配到右表数据时,字段天然为NULL。这时直接SUM(right_table.amount)不是数据丢了,是它被聚合函数自动跳过了。
- 典型场景:
orders LEFT JOIN coupons ON ...,coupon_discount为NULL表示没用券,业务上应计为0 - 必须写成:
SUM(COALESCE(coupon_discount, 0)),而不是COALESCE(SUM(coupon_discount), 0) - 更危险的是算式:
SUM(total - discount)中任一为NULL,整行结果变NULL;得拆成:SUM(COALESCE(total, 0) - COALESCE(discount, 0))
WHERE里慎用COALESCE,索引大概率失效
COALESCE放在SELECT列里很轻量,但一旦进WHERE,数据库通常没法用上索引。
- 危险写法:
WHERE COALESCE(status, 'active') = 'active'→ 全表扫描风险高 - 推荐写法:
WHERE status = 'active' OR status IS NULL→ 条件可下推,能走索引 - 如果真要高频查“空或某值”,不如建计算列+索引,例如 PostgreSQL:
CREATE INDEX ON t ((COALESCE(status, 'active')))
最麻烦的从来不是NULL本身,而是同一列里有的NULL表示“未发生”,有的表示“不适用”,有的表示“录入失败”——这时候统一COALESCE(col, 0)就是在掩盖问题,而不是解决问题。

















