AVG()天然跳过NULL是ANSI SQL-92标准行为,因NULL表示“未知”而非0或空字符串;分母为非NULL值个数,非总行数;COALESCE(AVG(col), 0)仅兜底结果安全,而AVG(COALESCE(col, 0))会扭曲语义。

AVG() 忽略 NULL 是标准行为,不是 bug;但直接用 COALESCE(col, 0) 填 0 很可能引入更大偏差——得先搞清业务中 NULL 到底代表什么。
AVG() 为什么天然跳过 NULL?
这不是 MySQL 或某家数据库的“默认设置”,而是 ANSI SQL-92 标准第 10.9 条明确定义的:所有聚合函数(AVG、SUM、COUNT(column))在执行前自动过滤掉该列值为 NULL 的行。数学上,NULL 表示“未知”,不是零、不是空字符串、也不是缺失默认值——AVG(NULL, 5, 10) 没有定义解,所以被排除是严谨选择。
实际效果就是:AVG(col) = SUM(col) / COUNT(col),而不是 SUM(col) / COUNT(*)。分母永远是“非 NULL 值个数”,不是总行数。
COALESCE(col, 0) 看似简单,却常埋雷
把 NULL 强制转成 0 最快,但业务语义可能彻底错位:
- 销售表中
amount为NULL→ 表示“订单未创建/未落库”,不是“成交额为 0” - 用户表中
last_login为NULL→ 表示“从未登录”,不是“登录时间为 0 时间戳” - 传感器数据中
temperature为NULL→ 表示“设备离线/读数失败”,不是“温度为 0℃”
一旦用 AVG(COALESCE(amount, 0)),就把“未发生”强行等价于“发生了且为零”,均值会被系统性拉低,尤其当 NULL 占比高时(比如 40% 用户无订单),结果失真严重。
按业务含义选对策略
不能一概而论“填 0 还是删行”,得看 NULL 在你这张表里到底是什么意思:
- 如果
NULL= “未发生/不可用”(如未成交订单、未采集传感器值)→ 推荐显式分离统计:COUNT(*) AS total_rows, COUNT(amount) AS valid_count, AVG(amount) AS avg_of_valid - 如果
NULL= “应为 0 但漏录”(极少见,需 DBA 和业务方共同确认)→ 可用AVG(COALESCE(amount, 0)),但必须加注释并写进数据字典 - 如果要算“全体用户的期望值”(比如 LTV 模型输入),则分母必须是
COUNT(*):SUM(COALESCE(amount, 0)) / COUNT(*),而非AVG(COALESCE(amount, 0))(后者仍用COUNT(amount)当分母)
全 NULL 列返回 NULL,应用层不处理会崩
这是最隐蔽的线上故障点:SELECT AVG(score) FROM t WHERE user_id = 123,若该用户所有 score 都是 NULL,结果就是 NULL,不是 0。Java 用 getDouble()、Python 用 row[0] 直接取值,不判空就抛 NullPointerException 或 TypeError。
防御写法要带空值兜底:
SELECT IF(COUNT(score) > 0, AVG(score), 0) AS safe_avg FROM t WHERE user_id = 123;
或更通用的跨库写法:
SELECT COALESCE(AVG(score), 0) AS safe_avg FROM t WHERE user_id = 123;
注意:COALESCE(AVG(col), 0) 是安全的,因为只对最终聚合结果做兜底;而 AVG(COALESCE(col, 0)) 是语义重写,两者目的完全不同。
真正难的从来不是怎么写 SQL,而是和产品、BI、风控一起对齐:这一列的 NULL,在当前报表口径下,到底该参与计算、排除统计,还是替换成某个业务可解释的默认值。










