最稳妥做法是用COUNT() - COUNT(字段名)计算每组NULL数量,因COUNT()统计所有行而COUNT(字段名)跳过NULL,二者相减即得该字段在每组中的NULL个数,兼容所有SQL方言。

用 COUNT(*) - COUNT(字段名) 计算每组 NULL 数量
直接对字段用 COUNT() 会跳过 NULL,所以 COUNT(*) 减去 COUNT(字段名) 就是该字段在每组中的 NULL 个数。这是最稳妥、兼容所有 SQL 方言(MySQL、PostgreSQL、SQL Server、Oracle)的做法。
注意别用 COUNT(NULL) 或 SUM(CASE WHEN 字段 IS NULL THEN 1 ELSE 0 END)——前者恒为 0,后者虽可行但写法冗长且易出错。
- GROUP BY 后必须包含所有非聚合列,否则报错(如 PostgreSQL 严格模式)
- 如果字段类型是 TEXT / JSON / BLOB,某些旧版 MySQL 可能对
COUNT(字段名)行为异常,建议显式 CAST 或换用条件计数 - NULL 和空字符串
''不等价,这个公式只统计 NULL,不统计空字符串
用 SUM(CASE WHEN 字段 IS NULL THEN 1 ELSE 0 END) 按需分组统计
当需要同时统计 NULL、空字符串、非空值等多类情况,或者要配合其他条件(比如只统计某状态下的 NULL),SUM(CASE WHEN ...) 更灵活。它本质是把布尔判断转成数值再累加。
示例:按部门统计 phone 字段的 NULL 数量
SELECT dept, SUM(CASE WHEN phone IS NULL THEN 1 ELSE 0 END) AS null_count FROM employees GROUP BY dept;
- 务必写
ELSE 0,漏掉会导致 NULL 值被忽略,SUM 结果变小 - 在 SQLite 中,
IS NULL安全;但用= NULL永远不成立,这是初学者高频错误 - 如果字段有索引,
IS NULL在多数引擎中可走索引(如 PostgreSQL 的 BRIN 索引、MySQL 8.0+ 的函数索引),但需确认执行计划
WHERE 子句里不能直接用 COUNT() 过滤 NULL 组
新手常写 HAVING COUNT(col) = 0 想找“整组都是 NULL”的组,但这是错的——COUNT(col) 对全 NULL 组返回 0,确实能捕获,但语义模糊且不可靠。更危险的是想用 WHERE col IS NULL 后再 COUNT,这会先过滤掉非 NULL 行,导致结果不是“每组的 NULL 数”,而是“每组中被保留下来的行数”。
- 要筛选 NULL 数量 ≥ 3 的组,必须用
HAVING SUM(CASE WHEN col IS NULL THEN 1 ELSE 0 END) >= 3 -
HAVING是唯一能在分组后对聚合结果做条件的子句,WHERE作用于原始行,早于分组 - 某些方言(如老版本 HiveQL)不支持在 HAVING 中用表达式,需改用子查询包裹
不同数据库对 NULL 统计的细微差异
标准 SQL 规定 NULL 不参与任何比较和聚合运算,但各实现仍有边界差异。比如:
- MySQL 5.7 默认开启
sql_mode=STRICT_TRANS_TABLES时,GROUP BY中若 SELECT 列未出现在 GROUP BY 或聚合函数中,会报错;而 PostgreSQL 始终强制此规则 - Oracle 把空字符串
''当作 NULL 处理,所以COUNT(col)在 Oracle 中也会跳过空字符串,和其他数据库行为不一致 - SQLite 的
COUNT(*)性能极好,但COUNT(col)在大表上可能比SUM(CASE...)略快,因无需解析 CASE 分支
跨数据库迁移时,NULL 统计逻辑最容易暴露兼容性问题,建议在目标环境跑一遍 NULL 密集测试数据验证。

















