count(distinct column_name) 是最常用去重计数写法,统计指定列非空唯一值数量,自动忽略null;多列组合去重需写为count(distinct col1, col2),但postgresql等需改用子查询;null与空字符串不等价,需显式转换才可统一计数。

count(distinct column_name) 是最常用写法
统计去重后的行数,核心就是用 COUNT(DISTINCT ...)。它只对指定列(或列组合)中真正不同的值计数,自动跳过 NULL —— 这点很重要,很多人误以为它会把 NULL 当作一个独立值来计,其实不会。
常见错误现象:COUNT(DISTINCT column_name) 返回结果比预期少,往往是因为该列存在大量 NULL,而你本意是想把 NULL 也当作一种“去重后的类别”来统计。
- 单列去重计数:
SELECT COUNT(DISTINCT user_id) FROM orders; - 多列组合去重计数:
SELECT COUNT(DISTINCT user_id, status) FROM orders;(注意:MySQL 5.7+ 支持,但 PostgreSQL 和 SQL Server 要改写为子查询) - 想把
NULL当作一个有效值参与去重?得显式转换:COUNT(DISTINCT COALESCE(user_id, -1))或COUNT(DISTINCT CASE WHEN user_id IS NULL THEN 'NULL' ELSE CAST(user_id AS TEXT) END)
distinct + count(*) 子查询在跨数据库时更稳妥
某些场景下,比如要对多列去重后统计总行数,或者目标数据库不支持 COUNT(DISTINCT col1, col2)(如旧版 PostgreSQL),就得用子查询方式。
性能影响明显:子查询会先生成临时去重结果集,再套一层 COUNT(*),数据量大时比直接 COUNT(DISTINCT ...) 更慢,且无法利用索引优化。
- 标准写法:
SELECT COUNT(*) FROM (SELECT DISTINCT user_id, status FROM orders) AS t; - 别漏
AS t别名,否则 MySQL 8.0+ 会报错Every derived table must have its own alias - PostgreSQL 中可省略
AS,但加了更安全;SQL Server 要求必须有别名
group by + count(*) 不等于去重行数统计
有人看到“去重”,第一反应写 GROUP BY 再套 COUNT(*),这是典型误解。它统计的是每个分组内的行数,不是去重后的总组数。
想得到“有多少个不同 user_id”,写 SELECT COUNT(*) FROM (SELECT user_id FROM orders GROUP BY user_id) t; 才对;直接 SELECT COUNT(*), user_id FROM orders GROUP BY user_id 返回的是每组的明细行数,不是你要的总数。
- 错误示范:
SELECT COUNT(*), user_id FROM orders GROUP BY user_id;→ 返回多行,不是单个总数 - 正确但冗余:
SELECT COUNT(*) FROM (SELECT user_id FROM orders GROUP BY user_id) t;→ 等价于COUNT(DISTINCT user_id),但更啰嗦、更慢 - 如果还要同时查其他聚合字段(如每个 user_id 的订单数),才值得用
GROUP BY主体结构
NULL 和空字符串在 distinct 中是否等价?
答案是:不等价。空字符串 '' 和 NULL 在 DISTINCT 中被视为两个完全不同的值。这点常被忽略,导致统计偏差。
例如用户昵称字段,有的存了空字符串,有的是 NULL,COUNT(DISTINCT nickname) 会把它们算作两个不同值。如果你业务上认为“没填昵称”就该归为一类,得提前清洗:
- 统一转为空字符串:
COUNT(DISTINCT COALESCE(nickname, '')) - 统一转为
NULL:COUNT(DISTINCT NULLIF(nickname, '')) - 或者业务层明确规范:入库时强制不允许空字符串,只允许
NULL
实际跑之前,先用 SELECT DISTINCT nickname FROM users LIMIT 10; 看一眼真实分布,比猜强得多。











