空值比例统计不能直接用COUNT()除以COUNT(字段),因COUNT(字段)自动忽略NULL导致分母偏小;正确做法是用COUNT()作分母,分子用COUNT(CASE WHEN 字段 IS NULL THEN 1 END)或SUM(CASE WHEN 字段 IS NULL THEN 1 ELSE 0 END)统计空值行数,并注意空字符串需单独判断。

空值比例统计为什么不能直接用 COUNT(*) 除以 COUNT(字段)
因为 COUNT(字段) 会自动忽略 NULL,而 COUNT(*) 统计所有行,表面看像是“非空行数”,但一旦字段本身允许 NULL,且你真正想算的是“该字段为空的占比”,直接相除会把分组内所有 NULL 行排除在分母外——结果分母变小,比例失真。比如某组有 10 行,其中 4 行 status 为 NULL,那空值比例应是 4/10 = 40%;但 COUNT(status) 是 6,COUNT(*) 是 10,1 - COUNT(status)/COUNT(*) 才对,而不是 COUNT(*)/COUNT(status)。
更稳妥的做法是统一用 COUNT(*) 当分母,分子显式统计 NULL 行数。
CASE WHEN 统计空值的写法要点
核心是:在 COUNT 内部嵌套 CASE WHEN 字段 IS NULL THEN 1 END,让 COUNT 只对满足条件的行计数(COUNT 忽略 NULL,所以 ELSE 不写或写 NULL 都行)。
-
COUNT(CASE WHEN status IS NULL THEN 1 END)→ 统计status为空的行数 -
COUNT(*)→ 当前分组总行数(必须和GROUP BY对齐) - 最终比例建议转成
DECIMAL或乘以1.0避免整数截断:例如ROUND(COUNT(CASE WHEN status IS NULL THEN 1 END) * 100.0 / COUNT(*), 2) - 如果字段是字符串类型,注意
''(空字符串)不等于NULL,需额外判断:CASE WHEN status IS NULL OR status = '' THEN 1 END
按多字段分组时,CASE WHEN 和 GROUP BY 的对齐陷阱
常见错误是 GROUP BY 漏掉参与计算的字段,或在 CASE WHEN 中引用了未出现在 GROUP BY 中的非聚合字段,导致 SQL 报错(如 PostgreSQL 的 “column must appear in the GROUP BY clause” 或 MySQL 严格模式下的错误)。
实操建议:
- 先写好
GROUP BY子句,确保它包含所有用于分组的列(如category, region) -
CASE WHEN内只能引用这些分组字段、常量,或聚合函数结果 - 若要对比不同字段的空值率(如同时看
email和phone),每个都单独写一个COUNT(CASE...),不要试图复用 - 示例片段:
SELECT category, region, ROUND(COUNT(CASE WHEN email IS NULL THEN 1 END) * 100.0 / COUNT(*), 2) AS email_null_pct, ROUND(COUNT(CASE WHEN phone IS NULL THEN 1 END) * 100.0 / COUNT(*), 2) AS phone_null_pct FROM users GROUP BY category, region;
性能与可读性权衡:用 SUM 替代 COUNT 是否更好?
可以,而且更直观:SUM(CASE WHEN status IS NULL THEN 1 ELSE 0 END) 和 COUNT(CASE WHEN status IS NULL THEN 1 END) 结果一致,但 SUM 明确表达了“加总标记”,语义更清晰,也避免新手误以为 COUNT 能直接处理布尔表达式(它不能,COUNT(status IS NULL) 在多数数据库里语法报错或返回全 1)。
不过要注意:
-
SUM版本必须写ELSE 0,否则NULL项会被跳过,导致求和偏小 - 某些旧版 SQLite 或严格配置的数据库对
SUM(NULL)行为敏感,COUNT版更保守 - 执行计划上二者通常无差别,选哪个主要看团队规范和可维护性
空值比例看着简单,但分母是否包含过滤后的行、字段是否含空字符串、多维分组时的字段可见性——这些地方一不留神,报表数字就悄悄跑偏。










