COUNT(DISTINCT column) 直接统计某列非重复值总数,是轻量高效的标准写法;但旧版MySQL(如5.6)不支持,会报错,且不可在括号内加WHERE或ORDER BY。

count(distinct column) 直接统计不重复值数量
这是最轻量、最常用的去重计数方式,适用于只需要知道“有多少个唯一值”,而不需要具体是哪些值的场景。它不是先去重再查,而是直接在聚合过程中跳过重复项。
常见错误现象:COUNT(DISTINCT) 在旧版 MySQL(COUNT DISTINCT column(漏掉括号)会报语法错误 ERROR 1064。
- 必须用括号包裹列名:
COUNT(DISTINCT user_id),不能写成COUNT DISTINCT user_id - 支持多列组合去重计数,但语义是“组合唯一”:
COUNT(DISTINCT user_id, product_id)统计的是不同用户-商品对的数量 - NULL 值会被自动忽略——哪怕整列都是 NULL,结果也是 0,不会报错或计入
- 性能上,若
user_id有索引,COUNT(DISTINCT)通常能利用索引加速;否则可能触发临时表 + 文件排序,大数据量时明显变慢
distinct 配合 count(*) 在子查询中使用
当需要先按业务逻辑“去重取行”,再统计这些行的总数时(比如“每个用户最新一条订单的总条数”),就不能只靠 COUNT(DISTINCT),得先构造去重后的结果集。
使用场景:你关心的不是字段值的唯一性,而是“某组条件下应保留哪一行”,再数这些行。
- 典型结构是
SELECT COUNT(*) FROM (SELECT DISTINCT ... FROM ...) t,注意外层必须给子查询起别名(如t),否则 MySQL 8.0+ 会报Every derived table must have its own alias - 比起窗口函数方案,这种写法兼容性更好(支持所有 SQL 标准数据库),但无法控制保留哪条重复记录——
DISTINCT只保证组合唯一,不保证顺序 - 如果子查询里用了
ORDER BY,它对外层COUNT(*)没有意义,会被优化器忽略;真要按时间取最新,得用窗口函数 - 大数据量下,子查询生成中间结果集可能吃内存;可考虑加
LIMIT测试逻辑,但正式查询中LIMIT不能和COUNT(*)共存
group by + count(*) 替代 distinct 计数的边界情况
表面看 SELECT COUNT(*) FROM (SELECT col FROM t GROUP BY col) t 和 SELECT COUNT(DISTINCT col) FROM t 等价,但实际行为有关键差异。
容易踩的坑:MySQL 默认允许 SELECT col FROM t GROUP BY col,但 PostgreSQL、SQL Server 会直接报错,提示 column "col" must appear in the GROUP BY clause or be used in an aggregate function。
- 如果你的数据库严格遵循 SQL 标准(如 PostgreSQL),想用
GROUP BY实现去重计数,必须写成SELECT COUNT(*) FROM (SELECT 1 FROM t GROUP BY col) t,用常量1避开非聚合列限制 -
GROUP BY方案天然支持附加条件,比如 “只统计 status = 'active' 的唯一 user_id 数量”:SELECT COUNT(*) FROM (SELECT user_id FROM t WHERE status = 'active' GROUP BY user_id) t - 性能上,
GROUP BY通常比COUNT(DISTINCT)开销更大,因为要建哈希表或排序;但如果已有col上的索引,且数据分布均匀,两者差距不大
窗口函数 count() over (partition by ...) 不适合做去重计数
有人看到 COUNT(*) OVER (PARTITION BY user_id) 就想拿来统计唯一用户数,这是典型误解。
这个表达式返回的是每行所在分组的行数(即每个 user_id 出现了多少次),不是全局唯一值个数。它不能替代 COUNT(DISTINCT),也不能直接用于去重计数。
- 若你执行
SELECT COUNT(*) OVER (PARTITION BY user_id) FROM t,结果是一列数字(比如全是 3、2、3…),不是单个总数 - 强行套
MAX(COUNT(*) OVER (...))或MIN(...)毫无意义——它们统计的是“某个用户最多出现几次”,不是“有多少个用户” - 真正要用窗口函数做去重计数,得配合
ROW_NUMBER()先标记首行,再在外层COUNT(*):例如SELECT COUNT(*) FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY id) rn FROM t) t WHERE rn = 1,但这明显比COUNT(DISTINCT user_id)冗余
138****1234 vs +86-138****1234),这时任何 DISTINCT 或 GROUP BY 都无效,得先标准化再统计。

















