在数据分析与业务报表开发中,经常需要统计某列中不重复值的数量,例如计算独立用户数、不同商品种类数等。SQL提供的COUNT(DISTINCT 列名)语法能够在聚合查询中直接完成去重计数,避免先查明细再在应用层去重的低效做法。

一、COUNT(DISTINCT)基本语法与原理
COUNT(DISTINCT expression)会对expression求值后的结果集进行去重,然后返回非NULL唯一值的个数。数据库通常会在执行时构建哈希表或使用排序归并的方式剔除重复项,再对去重后的集合计数。这与COUNT(列名)只跳过NULL但不去重的行为完全不同。
以下示例统计订单表中购买了商品的独立用户数量:
-- 统计有多少不同的用户下过订单 SELECT COUNT(DISTINCT user_id) AS unique_users FROM orders;
如果user_id存在重复,上述语句只会把每个用户计算一次。与之对比,COUNT(user_id)会统计所有非NULL的订单记录行数,两者结果可能相差数倍。在用户行为日志、交易流水等明细表中,这一差异直接影响报表准确性。
二、配合WHERE与GROUP BY的使用
COUNT(DISTINCT)可以结合过滤条件和分组操作,实现分维度去重计数。比如在统计各地区独立买家数时,先按地区分组,再在组内对user_id去重。
-- 每个城市的独立买家数
SELECT city,
COUNT(DISTINCT user_id) AS buyer_count
FROM orders
WHERE order_status = 'paid'
GROUP BY city;
这里WHERE先过滤出已支付订单,GROUP BY city将数据按城市切分,每个分组内独立执行去重计数。需要注意的是,不同数据库对GROUP BY后使用DISTINCT的优化程度不同,在MySQL中该写法较为常见,而在部分分析型数据库中可用APPROX_COUNT_DISTINCT提升性能。
有时开发者误以为可以先GROUP BY user_id再COUNT,虽然结果一致但语义和执行路径不同。先去重计数通常更节省应用层传输,但若还需用户明细则应采用子查询方式。
三、多列联合去重与NULL处理
标准SQL的COUNT(DISTINCT)仅支持单列去重。如果需要按多列组合去重,例如统计独立的三元组(用户、商品、日期),应采用子查询或CONCAT方式构造联合键。
-- 统计用户-商品组合的独立购买次数
SELECT COUNT(*) AS unique_pairs
FROM (
SELECT DISTINCT user_id, product_id
FROM order_items
) t;
上例通过内层DISTINCT对两列联合去重,外层COUNT(*)统计去重后的行数。另一种写法是拼接字段,但要注意分隔符避免碰撞:
-- 使用拼接模拟多列去重(需防冲突) SELECT COUNT(DISTINCT user_id || '_' || product_id) FROM order_items;
关于NULL值,COUNT(DISTINCT col)会忽略col为NULL的行,不会将多个NULL计为1。如果业务需要将NULL也视为一个独立类别,可用COALESCE转为特定占位符。
四、性能问题与优化思路
由于去重需要额外内存构建哈希或排序,COUNT(DISTINCT)在亿级大表上可能成为慢查询源头。常见优化包括:在允许误差时改用近似算法;对常统计维度建立物化视图;或预处理明细表减少扫描量。
| 方式 | 准确性 | 适用场景 |
|---|---|---|
| COUNT(DISTINCT) | 精确 | 中小表、强一致报表 |
| APPROX_COUNT_DISTINCT | 近似 | 大表监控、趋势分析 |
| 子查询预去重 | 精确 | 多列联合、复杂逻辑 |
在编写查询时,应避免在SELECT中混用多个不同列的COUNT(DISTINCT),因为部分数据库无法并行处理多个去重哈希,会导致性能陡降。此时可拆分为多个子查询再关联。
去重计数的核心在于明确业务上的“唯一”定义,再选择对应的SQL构造方式,而非盲目套用函数。
五、常见错误与纠正
新手常写COUNT(DISTINCT *)试图对所有列去重,这在标准SQL中属于语法错误。还有人把DISTINCT放在COUNT外,如DISTINCT COUNT(user_id),这只会去重计数结果本身,毫无意义。
-- 错误:DISTINCT位置不对 SELECT DISTINCT COUNT(user_id) FROM orders; -- 正确:去重在内 SELECT COUNT(DISTINCT user_id) FROM orders;
另一个易错点是在窗口函数中使用COUNT(DISTINCT)时,部分数据库(如MySQL旧版)不支持,此时需用子查询或数组函数替代。理解这些边界能让去重计数查询既正确又高效。
SQLCOUNT_DISTINCT去重计数修改时间:2026-08-09 03:06:12