COUNT(DISTINCT column)统计该列在分组内去重后的非NULL值个数,忽略NULL,不判断整行重复,字符串比较依数据库排序规则,不支持直接用于窗口函数,需配合GROUP BY使用。

SQL中COUNT(DISTINCT column)到底统计什么
COUNT(DISTINCT column) 统计的是该列在分组内去重后的非NULL值个数。它不关心整行是否重复,只看指定列的值有多少种不同取值。
常见误解是以为它能“找出唯一记录”,其实它只是计数——比如用户表按部门分组后,COUNT(DISTINCT job_title) 返回的是这个部门里有多少种不同的职位,不是“有多少人职位不重复”。
- NULL值会被自动忽略(不会参与去重,也不计入结果)
- 字符串比较区分大小写(取决于数据库排序规则,MySQL默认不区分,PostgreSQL区分)
- 不能直接用在窗口函数中作为
COUNT(DISTINCT ...)OVER(...)(多数数据库不支持,需改用子查询或近似方案)
GROUP BY配合COUNT(DISTINCT ...)的正确写法
必须和GROUP BY一起用才能体现“分组中”的语义,否则就是全表去重计数。
SELECT department, COUNT(DISTINCT job_title) AS distinct_jobs FROM employees GROUP BY department;
如果漏掉GROUP BY department,结果只有一行,是整个表里所有部门合并计算的去重职位数。
- SELECT列表中所有非聚合字段都必须出现在
GROUP BY中(否则MySQL 5.7+严格模式报错,PostgreSQL直接拒绝) - 想同时查分组总数和去重数,可以混用:
COUNT(*)和COUNT(DISTINCT ...) - 多个字段去重?写成
COUNT(DISTINCT first_name, last_name)—— 注意:这是组合去重(MySQL 8.0+/PostgreSQL支持,SQLite不支持)
替代方案:当COUNT(DISTINCT)性能差或不支持时
在大数据量或旧版本MySQL(如5.6)中,COUNT(DISTINCT) 可能触发临时表+文件排序,比子查询还慢;某些场景下也受限于语法(如窗口函数)。
- 用子查询去重再计数:
SELECT department, COUNT(*) AS distinct_jobs FROM (SELECT DISTINCT department, job_title FROM employees) t GROUP BY department;
- 用
ROW_NUMBER()模拟(仅限支持窗口函数的数据库):SELECT department, COUNT(*) FROM (SELECT department, job_title, ROW_NUMBER() OVER (PARTITION BY department, job_title ORDER BY 1) AS rn FROM employees) t WHERE rn = 1 GROUP BY department; - 注意:子查询方式可能丢失原始分组中的空值处理逻辑,且无法直接复用WHERE条件,需谨慎下推过滤条件
容易被忽略的NULL和类型隐式转换问题
COUNT(DISTINCT) 对NULL完全静默,但有时候你其实需要知道“有没有NULL”或者“NULL算不算一种取值”。这时候它就帮不上忙。
- 数值字段存了字符串'null'或空字符串'',会被当作普通值参与去重,和真正的NULL不同
- 日期字段用
CAST(created_at AS DATE)后再DISTINCT,要注意时区和截断精度(比如'2024-01-01 23:59:59'和'2024-01-01 00:00:01'会变成同一个DATE) - JSON字段、数组字段等复杂类型,多数数据库不支持直接
DISTINCT,需先提取成标量再统计
真正难的不是写对语法,而是确认业务上“不重复”究竟指什么——是字段值本身唯一?还是字段值+上下文组合唯一?别让COUNT(DISTINCT)替你做业务判断。

















