count(distinct column)直接忽略null值,整列全为null时返回0,仅1行非null时返回1,这是mysql 5.7+、postgresql、sql server、oracle的统一行为,并非bug。

字段里有NULL,COUNT(DISTINCT)直接跳过
COUNT(DISTINCT column) 不会把 NULL 当作一个值去重,而是彻底忽略——哪怕整列只有 1 行非 NULL 值、其余全是 NULL,结果也是 1;如果全为 NULL,结果就是 0。这不是 bug,是所有主流数据库(MySQL 5.7+、PostgreSQL、SQL Server、Oracle)的统一行为。
容易踩的坑:
- 误以为多个 NULL 会被算作“1 个去重值”,实际是 0 个
- 用
SELECT COUNT(DISTINCT user_id)统计活跃用户,但埋点日志里大量缺失user_id(填了 NULL 或空字符串),结果严重偏低 - 没做前置校验,直接拿结果交付,被业务方质疑“为什么比人工数少一半”
验证方法:运行 SELECT COUNT(*), COUNT(user_id), COUNT(DISTINCT user_id) FROM table_name,三者差距大就说明 NULL 或空值干扰严重。
GROUP BY 分组太细,每组只剩一个值
当你写 SELECT category, COUNT(DISTINCT user_id) FROM orders GROUP BY category, order_id,问题不在 COUNT(DISTINCT) 本身,而在 order_id 是唯一键——分组粒度细到每行一个组,自然每个组里 user_id 最多出现一次,COUNT(DISTINCT user_id) 全是 1。
实操建议:
- 检查
GROUP BY子句是否混入了高唯一性字段(如id、created_at、uuid) - 只保留业务语义上的分组维度,例如
GROUP BY date, region,而不是GROUP BY date, region, event_id - 用
SELECT COUNT(*)和COUNT(DISTINCT user_id)同组对比,若前者远大于后者,大概率是分组过细
JOIN 导致数据膨胀,DISTINCT 被迫兜底但掩盖逻辑缺陷
COUNT(DISTINCT user_id) 在关联查询中常被当成“保险丝”,但它救不了错误的 JOIN 逻辑。比如 users LEFT JOIN events ON users.id = events.user_id,如果一个用户有 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 匹配时
多列去重写法错或数据库不支持
COUNT(DISTINCT a, b) 看似直观,但兼容性很碎:
- MySQL 8.0+ 和 PostgreSQL 支持,写法就是
COUNT(DISTINCT a, b) - MySQL 5.7 及更早版本不支持,报错
ERROR 1064 - SQL Server 和 Oracle 完全不支持多列直接进
COUNT(DISTINCT),必须用子查询:SELECT COUNT(*) FROM (SELECT DISTINCT a, b FROM t) s - 别写成
COUNT(DISTINCT (a, b))—— 这在 PostgreSQL 是合法行构造器,但在 MySQL/SQL Server 里直接语法错误
另外,COUNT(DISTINCT a, b) 统计的是组合唯一性,不是 COUNT(DISTINCT a) + COUNT(DISTINCT b),这点极易误解。











