avg() 必须跳过 null 是因 sql 标准规定其语义为“对已知值求平均”,null 代表未知,故自动剔除并以 count(col) 为分母;整列 null 时返回 null 而非 0;coalesce(avg(col), 0) 仅兜底空组,avg(coalesce(col, 0)) 则错误将 null 当 0 计入,扭曲业务含义。

AVG() 忽略 NULL 是 ANSI SQL 标准强制行为,不是数据库“优化”或 bug;真正出问题的地方,几乎都出在业务语义混淆、兜底方式错位、或者压根没查分母大小。
AVG() 为什么必须跳过 NULL
因为 NULL 在 SQL 中代表“未知”,不是 0、不是空字符串、也不是 false。AVG() 的语义是“对已知值求平均”,所以它按标准第 10.9 条自动剔除所有 col IS NULL 的行,再用 COUNT(col) 当分母——这和 COUNT(*) 完全不同。
- 数据为
[10, NULL, 20, 0]时,AVG(col)算的是(10 + 20 + 0) / 3 = 10,0 参与,NULL 不参与 - 整列都是 NULL →
AVG(col)返回NULL(数学上无定义),不是 0,也不报错 -
WHERE col IS NOT NULL是冗余的:结果一样,但会整行过滤,破坏同查询中COUNT(*)、关联字段或后续条件的上下文
COALESCE(AVG(col), 0) 和 AVG(COALESCE(col, 0)) 完全是两回事
前者只在整组结果为 NULL 时兜底补 0,不改变原始计算逻辑;后者先把每个 NULL 强行替成 0,再算平均——分母变大,均值被拉低,业务含义可能彻底失真。
- ✅
COALESCE(AVG(score), 0):某部门全员未填绩效,报表显示 0 而非空白,适合前端防崩 - ❌
AVG(COALESCE(score, 0)):把“未打分”当成“打了 0 分”,LTV、考核分等模型直接偏移 - 传感器
temp为 NULL 表示“设备离线”,填 0 后参与计算 → 引入虚假低温偏差
想排除 0 值?别用 WHERE,用 NULLIF()
当 0 是无效占位符(如 API 耗时为 0、测试订单金额为 0),而字段本身又允许 NULL,WHERE col != 0 会整行过滤,影响 COUNT(*)、MAX(created_at) 等其他聚合。
- ✅
AVG(NULLIF(col, 0)):0 变成 NULL,AVG()自动跳过,行结构保留 - ❌
WHERE col != 0:丢弃整行,分组完整性被破坏,负值(如温度变化 -2.5)也会被误删 - LEFT JOIN 后字段天然为 NULL(比如优惠券折扣没用),应写
SUM(COALESCE(coupon_discount, 0)),而非COALESCE(SUM(coupon_discount), 0)
光看 AVG() 永远不知道它除以几
这是最隐蔽的失真来源。同一列,COUNT(*)、COUNT(col)、COUNT(NULLIF(col, 0)) 可能完全不同——而 AVG(col) 的分母永远是 COUNT(col)。
- 必须显式查:
SELECT AVG(score), COUNT(*), COUNT(score), COUNT(NULLIF(score, 0)) FROM students - 如果
COUNT(score)远小于COUNT(*),说明数据缺失严重,不能单凭平均值做决策 - 子查询或视图里嵌套
AVG()时,外部再加WHERE,分母早已不是原始表的COUNT(*)
真正麻烦的从来不是 NULL 本身,而是同一列里有的 NULL 表示“未发生”,有的表示“不适用”,有的表示“录入失败”——这时候统一 COALESCE(col, 0) 反而掩盖了数据质量问题。










