count(distinct)是标准sql中唯一直接支持去重计数的聚合函数,不可被group by+count(*)替代,因其统计的是值去重后的数量而非分组数,且支持多列去重、窗口函数,而group by无法等效实现;它忽略null,主流数据库均支持(sqlite及旧版sql server除外),但性能开销大,因需维护去重集合;多指标去重时可考虑近似函数优化。

COUNT(DISTINCT) 是唯一能直接在聚合中去重计数的标准 SQL 方式,不是“可选方案”,而是事实标准。
为什么不能用 GROUP BY + COUNT(*) 替代 COUNT(DISTINCT)
GROUP BY 统计的是分组数量,不是去重后的值数量。比如 SELECT COUNT(*) FROM (SELECT DISTINCT col FROM t) 看似等价,但会多一层子查询、无法在窗口函数中使用、且不支持多列去重(DISTINCT a, b 合法,而 GROUP BY a, b 再套 COUNT 就变成“组合出现次数”而非“组合去重数”)。
常见错误现象:SELECT user_id, COUNT(DISTINCT order_id) FROM orders GROUP BY user_id 想统计每个用户有多少种不同商品,却错写成 COUNT(*),结果是订单数而非商品种类数。
-
COUNT(DISTINCT)对 NULL 值自动忽略(和大多数聚合函数一致) - MySQL 5.7+、PostgreSQL、SQL Server 2017+、Oracle、BigQuery 全部支持多列:如
COUNT(DISTINCT user_id, product_id)表示“不同用户-商品对”的总数 - SQLite 和旧版 SQL Server(2014 及以前)不支持多列
DISTINCT,会报错Incorrect syntax near ','
COUNT(DISTINCT) 的性能代价在哪
它必须在内存或临时磁盘中维护一个去重集合(类似哈希表或排序去重),数据量大时比普通 COUNT(*) 明显慢,尤其当去重字段无索引或选择性低(如只有 3 个取值的 status 字段)。
- PostgreSQL 中,如果字段有 B-tree 索引且查询条件能走索引,优化器可能用
Bitmap Heap Scan + HashAggregate加速;但没索引时大概率触发Seq Scan + HashAggregate,全表扫描不可避免 - MySQL 8.0+ 在
EXPLAIN中看到Using temporary; Using filesort就意味着正在建临时表去重 - 避免写
COUNT(DISTINCT *)—— 语法错误,DISTINCT后必须是具体列或表达式
替代方案:什么时候该放弃 COUNT(DISTINCT)
当单次查询要同时算多个去重指标(如 COUNT(DISTINCT a), COUNT(DISTINCT b), COUNT(DISTINCT c)),传统写法会触发三次独立去重,开销翻倍。这时应考虑:
- 用
APPROX_COUNT_DISTINCT()(BigQuery / Spark SQL)或APPROX_COUNT_DISTINCT()(Presto/Trino)换取速度与精度平衡,误差通常 - 在 PostgreSQL 中用
#(SELECT array_agg(DISTINCT x) FROM t)是错的——array_agg不去重,得配合UNNEST和GROUP BY,反而更慢 - 真正可控的优化是预计算:把高频去重维度(如每日活跃设备数)存到物化视图或汇总表,查时直接
SELECT count
多列去重的语义容易被误读:COUNT(DISTINCT a, b) 数的是 (a,b) 这一对的组合数,不是 a 和 b 各自去重后再加总。这个区别在设计宽表指标时,几乎每次都会有人掉坑里。










