count(列名)总比count()小,因为它只统计该列非null的行,而count()统计所有物理行;差值即该列null数量,空字符串、0、false均不被排除,仅数据库null被跳过。

结果不一致不是数据库出错,而是 COUNT 的语义被误读了——它从不“漏数”,只是严格按规则统计。
为什么 COUNT(列名) 总比 COUNT(*) 小?
因为 COUNT(列名) 只统计该列非 NULL 的行,COUNT(*) 统计所有物理行。差值就是该列 NULL 的数量。
- 空字符串
''、数值0、布尔false都算非NULL,会被计入COUNT(列名) - 只有数据库层面真正的
NULL(不是字符串'NULL')才会被跳过 -
COUNT(*) - COUNT(列名)是验证该列有多少NULL最直接的方法 - 在
LEFT JOIN后对右表字段用COUNT(右表.id),实际统计的是“关联成功的行数”,不是左表原始基数
COUNT(DISTINCT 列名) 为什么返回 0 或偏小?
COUNT(DISTINCT 列名) 彻底忽略 NULL,不把多个 NULL 当作一个去重值,而是当作不存在。
- 整列全是
NULL→ 返回0 - 只有一行非
NULL,其余全NULL→ 返回1 - 埋点日志中
user_id大量缺失(存为NULL),会导致活跃用户数严重偏低 - 若想让
NULL参与去重,需显式转换:如COUNT(DISTINCT COALESCE(user_id, '<null>'))</null>
GROUP BY + COUNT 为什么每组都返回 1?
分组粒度太细,导致每组只剩一行数据 —— 不是 COUNT 有问题,是 GROUP BY 混入了高唯一性字段。
- 常见错误:
GROUP BY category, order_id(order_id是主键)→ 每组仅 1 行 - 检查
GROUP BY子句是否包含id、created_at、uuid等唯一或近似唯一字段 - 对比
COUNT(*)和COUNT(DISTINCT user_id):若前者远大于后者,大概率是分组过细 - 业务分组应保留语义维度,例如
GROUP BY date, region,而非GROUP BY date, region, event_id
JOIN 后用 COUNT(DISTINCT) 是救火还是埋雷?
它能掩盖错误的 JOIN 逻辑,但代价是性能暴跌和指标不可靠。
-
users LEFT JOIN events中,1 个用户有 10 条事件 → 生成 10 行 →COUNT(DISTINCT user_id)虽返回正确人数,但底层已膨胀 10 倍 - 更稳妥的做法:把去重提前到子查询,如
SELECT COUNT(*) FROM (SELECT DISTINCT user_id FROM events WHERE ... ) t - 确保关联字段(如
events.user_id)有索引,否则子查询也慢 - 避免在
LEFT JOIN后直接写COUNT(DISTINCT 右表.列),尤其当右表匹配失败产生大量NULL时
最常被忽略的一点:NULL 判断发生在 WHERE 和 GROUP BY 之后 —— COUNT(列名) 反映的是当前结果集里该列的非空数量,不是原始表的分布。一旦在复杂查询里混淆这点,偏差不会报错,只会静默生效。











