NULL在GROUP BY中自成一组,COUNT(*)统计该组行数而COUNT(列名)返回0;需用COALESCE统一替换NULL再分组,且SELECT与GROUP BY中表达式必须一致;统计NULL行数应直接用WHERE或条件聚合。

GROUP BY 里 NULL 默认自成一组,但 COUNT(列名) 会跳过它
所有数据库(MySQL、PostgreSQL、SQL Server)在 GROUP BY col 时,都会把 col 为 NULL 的行聚合成单独一组——这点容易被当成“漏数据”,其实不是 bug,是标准行为。真正让人困惑的是:COUNT(*) 会统计这一组的行数,COUNT(col) 却返回 0,因为 COUNT() 只算非 NULL 值。
常见错误现象:
- 写
SELECT status, COUNT(status) FROM orders GROUP BY status,发现status IS NULL那组显示cnt = 0,误以为没数据 - 用
WHERE status != 'done'想包含NULL,结果查不到——因为NULL != 'done'是UNKNOWN,不进结果集
用 COALESCE 统一替换 NULL 再分组(跨数据库兼容)
COALESCE(col, 'unknown') 是最稳妥的写法,比 IFNULL()(MySQL 专属)或 ISNULL()(SQL Server 专属)更通用。关键是:必须在 SELECT 和 GROUP BY 中**都出现相同表达式**,不能只在 SELECT 里起别名然后 GROUP BY 别名。
正确写法示例:
SELECT COALESCE(status, 'unknown') AS status_group, COUNT(*) AS cnt FROM orders GROUP BY COALESCE(status, 'unknown');
注意事项:
- 如果
status是数字类型(如TINYINT),COALESCE(status, 'unknown')会触发隐式转字符串,影响ORDER BY排序(比如'10'排在'2'前面);此时改用COALESCE(CAST(status AS SIGNED), -1) - 占位符值(如
'unknown'或-1)必须和业务值不冲突,否则会混组
只想知道 NULL 有多少条?别硬塞进 GROUP BY
如果目标只是统计 status IS NULL 的行数,直接用 WHERE 最清晰,性能也更好:
SELECT COUNT(*) FROM orders WHERE status IS NULL;
或者合并到主查询中,用条件聚合:
SELECT COUNT(*) AS total, SUM(CASE WHEN status IS NULL THEN 1 ELSE 0 END) AS null_count, COUNT(status) AS non_null_count FROM orders;
避免这种绕路写法:
-- ❌ 多余且易错:先 COALESCE 分组,再 WHERE status_group = 'unknown' SELECT cnt FROM ( SELECT COALESCE(status, 'unknown') AS g, COUNT(*) AS cnt FROM orders GROUP BY COALESCE(status, 'unknown') ) t WHERE g = 'unknown';
字符串字段分组时,'' 和 NULL 默认不归一组
''(空字符串)和 NULL 在绝大多数数据库中是两个不同值,GROUP BY 会分出两组。如果你希望它们统一处理,得显式合并:
SELECT COALESCE(NULLIF(name, ''), 'unknown') AS name_group, COUNT(*) FROM users GROUP BY COALESCE(NULLIF(name, ''), 'unknown');
说明:
-
NULLIF(name, '')把空字符串转成NULL,再用COALESCE统一替换成占位符 - 大小写敏感性由字段 collation 决定,不要依赖
LOWER(name)分组——除非建了函数索引,否则无法走索引
真正麻烦的不是语法怎么写,而是不同场景下 NULL 的语义差异:是“未填写”“校验失败”还是“不适用”?同一字段的 NULL 可能混合多种原因,强行统一替换反而掩盖问题。先确认上游数据质量,比写十个 COALESCE 更重要。

















