COUNT(*)统计所有行,COUNT(列名)仅统计该列非NULL行;GROUP BY中NULL被归为一组但易被忽略;NOT IN遇NULL返回UNKNOWN致结果为0;DISTINCT计数自动跳过含NULL的行。

COUNT(*) 和 COUNT(列名) 的行为差异必须分清
统计结果偏少,八成是因为误用了 COUNT(列名)。它只数该列非 NULL 的行,而 COUNT(*) 才是真正“数行”的操作——哪怕整行所有字段都是 NULL,也会计入。
实操建议:
- 查总数一律用
COUNT(*),别图省事写COUNT(1)或COUNT(id),可读性差且无收益 - 想确认某列 NULL 比例?直接对比:
SELECT COUNT(*), COUNT(字段名) FROM 表名 - 若业务上“空字符串”也算无效值,不能只靠
IS NULL,得加判断:COUNT(CASE WHEN 字段名 IS NOT NULL AND 字段名 != '' THEN 1 END)
GROUP BY 中 COUNT 出现“少算”时先看分组键是否含 NULL
GROUP BY 遇到 NULL 值会把所有 NULL 归为同一组,但这个组容易被忽略——尤其当你要按某个维度统计分布,而该字段大量为 NULL 时,NULL 组可能不显示(取决于客户端或驱动),造成“总数对不上”。
实操建议:
- 执行前先跑
SELECT 字段名, COUNT(*) FROM 表名 GROUP BY 字段名,观察NULL是否单独成一行 - 需要显式标出
NULL组?用COALESCE(字段名, '[NULL]')替换,避免分组丢失 - 多字段组合分组时,任一字段为
NULL就会导致整条记录不参与COUNT(DISTINCT 字段1, 字段2),这是硬限制,没法绕开
NOT IN 和子查询带 NULL 是 COUNT 失准的隐藏推手
表面看是 COUNT 错了,实际可能是外层 WHERE 被 NOT IN (子查询) 拦腰截断:只要子查询结果里有任意一个 NULL,整个 NOT IN 表达式恒为 UNKNOWN,导致零行返回,COUNT 自然得 0。
实操建议:
- 检查子查询是否可能返回
NULL,例如SELECT user_id FROM orders WHERE status = 'cancelled'—— 若user_id允许为空,结果就危险 - 立刻替换为
NOT EXISTS,它对NULL不敏感:WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.status = 'cancelled') - 实在要用
IN/NOT IN,子查询末尾加WHERE 字段名 IS NOT NULL过滤掉 NULL
DISTINCT 计数在 NULL 存在时逻辑会悄然变化
COUNT(DISTINCT 列名) 本身会跳过 NULL;更隐蔽的是 COUNT(DISTINCT a, b):只要 a 或 b 任一为 NULL,整行就不参与去重计数——哪怕另一列值完全不同。
实操建议:
- 验证去重基数:先
SELECT DISTINCT a, b FROM 表名,肉眼扫一遍有没有整行是(NULL, xxx)或(xxx, NULL) - 需要把
NULL当作有效类别参与去重?用COALESCE显式转换:COUNT(DISTINCT COALESCE(a, '<null_a>'), COALESCE(b, '<null_b>'))</null_b></null_a> - 线上表设计阶段就约束关键字段为
NOT NULL,比后期补救成本低得多
最易被忽略的一点:NULL 不是值,是“缺失”的标记。所有聚合、比较、逻辑运算碰到它都会降级为三值逻辑,而多数人仍按二值逻辑写条件。别指望数据库替你猜意图——显式处理才是唯一稳态。


















