聚合函数跳过null是设计选择而非bug,因null代表“未知”,sum、avg、max、min、count(列名)跳过它以避免主观假设;count(*)统计所有行,coalesce与聚合顺序不同语义相反,left join后需用coalesce预处理,where中慎用coalesce以防索引失效,且null混杂多重语义时盲目填充会掩盖数据问题。

聚合函数跳过NULL不是bug,是设计选择
因为NULL在SQL里代表“未知”,不是0、不是空字符串、也不是false。SUM、AVG、MAX、MIN、COUNT(列名)跳过它,是为了避免用主观假设污染统计结果——比如AVG(age)遇到NULL,数据库不会猜这个人是0岁还是80岁,干脆不参与计算。
唯一例外是COUNT(*),它统计所有行,包括全字段为NULL的行;而COUNT(status)只数status IS NOT NULL的行,差值就是该列NULL数量。
整列都是NULL时,SUM()、AVG()、MAX()、MIN()都返回NULL,不是0,下游代码直接调用getInt()或doubleValue()会抛异常。
SUM(COALESCE(col, 0)) 和 COALESCE(SUM(col), 0) 完全不同
这两个写法看着像,但语义相反,换错就导致业务出错:
-
SUM(COALESCE(sales, 0)):先把每行sales为NULL的补成0,再加总。适合库存汇总、订单金额兜底等“缺记录就当0件”的场景 -
COALESCE(SUM(sales), 0):先加总,发现结果为NULL(比如某部门没人填sales)再补0。适合部门报表、分组统计中“空组要显示0”的需求
更危险的是表达式运算:SUM(total - discount)中任一为NULL,整行变NULL;必须拆成SUM(COALESCE(total, 0) - COALESCE(discount, 0))。
LEFT JOIN后字段为NULL,聚合前必须清洗
JOIN未匹配到右表数据时,字段天然为NULL。这时直接SUM(right_table.amount)会漏掉这些行——不是数据丢了,是它们被跳过了。
典型例子:订单主表LEFT JOIN优惠券表,coupon_discount为NULL表示没用券,业务上应计为0:
- ✅ 正确:
SUM(COALESCE(coupon_discount, 0)) - ❌ 错误:
COALESCE(SUM(coupon_discount), 0)——没用券的订单仍被忽略,总和偏低
同理,AVG(COALESCE(salary, 0))会把[NULL, 10000]算成5000,但真实情况可能是两人均未发薪,不该拉低平均值。
WHERE里别用COALESCE或IFNULL,索引大概率失效
COALESCE和IFNULL在SELECT里很轻量,但一旦进WHERE子句,数据库通常无法使用索引:
- ❌
WHERE COALESCE(phone, '') = '138xxx'→ 全表扫描 - ✅ 正确写法:
WHERE phone = '138xxx' OR (phone IS NULL AND '' = '138xxx')(极少这么写),更常用的是分开查或建函数索引
筛选NULL本身要用IS NULL,写WHERE col = NULL永远不成立;WHERE col IN (1, 2, NULL)实际只匹配1和2,最后一项恒为UNKNOWN。
真正麻烦的从来不是NULL怎么填,而是同一列里NULL混着三种含义:“未发生”“不适用”“录入失败”——这时候统一COALESCE(col, 0)可能掩盖数据质量问题。











