COUNT(DISTINCT column) 能实现去重后分组统计,但GROUP BY字段必须与业务语义对齐,否则因分组过细导致结果偏小;需检查是否混入高粒度字段,统一处理NULL和空字符串,注意大小写及多列去重兼容性。

直接用 COUNT(DISTINCT column) 就能实现“去重后分组统计”,但前提是 GROUP BY 的维度必须和业务语义对齐,否则结果会严重失真。
GROUP BY 字段选错导致 COUNT(DISTINCT) 结果偏小
常见现象是:明明某 category 下有 100 个不同 user_id,COUNT(DISTINCT user_id) 却只返回 2–3。根本原因不是函数失效,而是你在 GROUP BY 里混入了高粒度字段(比如 order_id、created_at 或带毫秒的时间戳),把本该归为一组的数据切成了几十个极细的组。
- 检查
GROUP BY子句是否只包含业务上真正定义“分组”的字段,例如category、date_trunc('day', created_at),而不是原始created_at - 用
COUNT(*)和COUNT(DISTINCT user_id)并列查同一组,如果前者远大于后者,说明分组过细 - 若需保留明细字段(如订单创建时间)又想按粗粒度统计,先用 CTE 或子查询聚合到正确粒度,再对外层结果做
COUNT(DISTINCT)
MySQL 8.0+ 和 PostgreSQL 中 DISTINCT 的行为差异
COUNT(DISTINCT column) 在主流数据库中语法一致,但底层处理逻辑和限制点不同:
- MySQL 8.0 默认开启
ONLY_FULL_GROUP_BY,若 SELECT 中出现未出现在 GROUP BY 中的非聚合字段,会直接报错;不能靠关掉这个模式来“绕过”,否则结果不可靠 - PostgreSQL 允许在 SELECT 中写非 GROUP BY 字段(只要它是函数依赖的),但
COUNT(DISTINCT)本身不解决字段依赖问题 - MySQL 对字符串列使用前缀索引时(如
INDEX(name(10))),COUNT(DISTINCT name)实际只基于前 10 字符去重,可能漏判长字符串差异
NULL 和空字符串对去重统计的影响
COUNT(DISTINCT column) 默认忽略 NULL,但不会忽略空字符串 '' —— 它们被当作有效值参与去重,容易导致统计膨胀或语义错误。
- 若业务上
NULL和''都代表“缺失”,应在聚合前统一清洗:WHERE column IS NOT NULL AND column != '' - 若需把
NULL当作一类独立分类统计,改用COUNT(DISTINCT COALESCE(column, 'NULL')) - 大小写敏感场景(如
'Apple'和'apple')需提前标准化:COUNT(DISTINCT LOWER(column))
多列组合去重统计的兼容性写法
想统计 “地区+产品类型” 这种组合的唯一数量,COUNT(DISTINCT region, product_type) 在 PostgreSQL 和 MySQL 8.0+ 可用,但在 SQLite、SQL Server 或旧版 MySQL 中会报错。
- 最通用写法是子查询:
SELECT COUNT(*) FROM (SELECT DISTINCT region, product_type FROM sales) AS t - 注意:大数据量下该写法会生成完整中间结果集,性能下降明显;如有复合索引
(region, product_type),PostgreSQL/MySQL 可走索引扫描加速DISTINCT - 避免用
GROUP BY region, product_type再套一层COUNT(*),纯属冗余,还多返回 N 行无用数据
真正难的不是写对那条 SQL,而是确认 GROUP BY 的字段是否真实对应业务分组意图——一个多余的字段,就足以让整个去重统计失去意义。

















