count(distinct column)是唯一可靠去重统计方式,语义清晰、null安全、优化充分;多列去重需写为count(distinct col1, col2),where条件须置于外层,性能问题优先查执行计划。

COUNT(DISTINCT column) 是唯一可靠去重统计方式
直接用 COUNT(DISTINCT column),别试图用 GROUP BY + 外层 COUNT(*) 包裹,那不是“去重统计”,是绕路且易错。数据库执行计划里,DISTINCT 由聚合引擎原生支持,语义清晰、优化充分;手写子查询反而可能漏掉 NULL 处理或引发临时表膨胀。
常见错误现象:SELECT COUNT(*) FROM (SELECT DISTINCT col FROM t) AS tmp 看似等价,但当 col 允许为 NULL 时,两种写法结果一致(DISTINCT 自动过滤 NULL),可读性和维护性却差一截;更糟的是,有人误写成 COUNT(DISTINCT *)——语法直接报错,DISTINCT 后必须跟具体列或表达式。
-
COUNT(DISTINCT col)对NULL安全:自动忽略NULL值,不参与计数 - 多列去重需写成
COUNT(DISTINCT col1, col2),不是COUNT(DISTINCT (col1, col2)) - MySQL 5.7+ 和 PostgreSQL 支持多列
DISTINCT;SQLite 支持;SQL Server 2019+ 才支持,旧版只能拼接字符串模拟(不推荐)
遇到 COUNT(DISTINCT) 性能差,先查执行计划再动手
慢不是语法问题,是索引或数据分布问题。COUNT(DISTINCT) 在无索引列上会强制扫描全表并建哈希表,尤其在千万级表中可能秒变分钟级。别急着加索引或改写 SQL,先看 EXPLAIN 输出里有没有 Using temporary 或 Using filesort。
使用场景判断:
- 如果只是偶尔查、且结果缓存可用(比如报表后台),加个应用层缓存比优化 SQL 更快
- 如果该列基数低(如状态码只有 3–5 个值),建普通 B-Tree 索引即可显著提速
- 如果基数高(如用户邮箱),考虑是否真需要实时精确值——有些场景用 HyperLogLog 近似算法(PostgreSQL 的
HLL扩展、Redis 的PFCOUNT)更合适
WHERE 条件必须写在 COUNT(DISTINCT) 外层,不能塞进括号里
COUNT(DISTINCT column WHERE condition) 是无效语法,所有主流 SQL 方言都不支持这种“条件去重”。正确做法是把过滤逻辑提前到 WHERE 子句,或用 CASE WHEN 构造中间值。
例如:统计“已支付订单中不同用户的数量”,不能写 COUNT(DISTINCT user_id WHERE status = 'paid')。正确写法有两种:
- 推荐:加前置过滤
SELECT COUNT(DISTINCT user_id) FROM orders WHERE status = 'paid' - 需要复用同一张表做多维度统计时,用条件表达式:
COUNT(DISTINCT CASE WHEN status = 'paid' THEN user_id END)—— 注意这里END后没有ELSE,让非匹配行返回NULL,自然被DISTINCT忽略
容易踩的坑:有人写 COUNT(DISTINCT CASE WHEN ... THEN user_id ELSE 0 END),结果把所有 ELSE 分支的 0 当作一个有效值计入,导致总数虚高。
嵌套聚合时 COUNT(DISTINCT) 不能直接出现在 GROUP BY 子句中
比如想按日期统计“每日去重用户数”,写成 SELECT date, COUNT(DISTINCT user_id) FROM t GROUP BY date 是合法且高效的;但若想进一步“统计每周去重用户数超过 100 的周数”,就不能写 SELECT COUNT(*) FROM (SELECT week, COUNT(DISTINCT user_id) cnt FROM t GROUP BY week) WHERE cnt > 100 —— 这本身没错,但要注意子查询里没加 WHERE 过滤原始数据,可能导致中间结果爆炸。
性能关键点:
- 外层聚合前,务必确认内层已尽可能过滤数据(比如加时间范围限制)
- 某些场景下,
APPROX_COUNT_DISTINCT()(BigQuery / SQL Server)或approx_count_distinct()(Spark SQL)能换得数量级性能提升,误差率通常 - MySQL 用户注意:
COUNT(DISTINCT)在GROUP BY中若涉及大字段(如长文本),可能触发tmp_table_size限制,报错ERROR 1105 (HY000): Unknown error,此时需调大配置或改用哈希截断预处理
NULL 的隐式行为和多列 DISTINCT 的括号陷阱——写完记得用小数据集验证结果是否符合预期,尤其当列可能为空或组合键含空值时。










