首页/数据/SQL 速查/DISTINCT 与 GROUP BY 去重的等价关系
聚合与窗口函数54 条

DISTINCT 与 GROUP BY 去重的等价关系

SELECT DISTINCT a, b 与 SELECT a, b ... GROUP BY a, b 在结果层等价;需要附加聚合时用 GROUP BY 更自然。

SQL 速查数据更新于 2026-10-13

两种去重的关系

SELECT DISTINCT a, b 与 SELECT a, b … GROUP BY a, b 在结果层面等价:都按这些列的组合去重,多个 NULL 也被当成同一组。区别在扩展能力:GROUP BY 能在同一层加聚合列(COUNT、SUM 等),DISTINCT 只能去重,不能顺带出统计值。

基本信息

结果行两者都按选定列的组合去重
扩展能力GROUP BY 可加聚合列,DISTINCT 不能
NULL 处理多个 NULL 视为同一组
严格模式改写后注意 ONLY_FULL_GROUP_BY 对非聚合列的限制
执行方式都可能有排序或临时表,也可借索引顺序优化

典型使用场景

  • 列出出现过的城市与状态组合
  • 需要「去重后再计数」时必须换成 GROUP BY
  • 按组合分组后加统计值再筛 HAVING
  • 排查 DISTINCT 结果行数比预期多或少的原因
  • 把报表口径从去重列表改成每组一行

示例

示例
-- 两种写法结果相同
SELECT DISTINCT city, status FROM users;

-- 等价写法
SELECT city, status FROM users GROUP BY city, status;

-- 需要附加聚合时必须用 GROUP BY
SELECT city, status, COUNT(*) AS cnt
FROM users
GROUP BY city, status
HAVING COUNT(*) > 10;   -- 只保留组合数足够多的分组

两种写法对照

维度SELECT DISTINCTGROUP BY
结果行去重后的组合每组一行
聚合列不能添加可以添加
筛选用法只能靠 WHEREWHERE 过滤行、HAVING 过滤组
NULL多个 NULL 视为同一组同样合并为一组
排序通常要额外排序或临时表同样,可借索引顺序省掉
DISTINCT 的去重范围是 SELECT 列表里的全部列:少写一列,结果行数就可能变多,这是「以为去重了其实没有」最常见的来源。
MySQL 的 ONLY_FULL_GROUP_BY 开启时,选择列表里的非聚合列必须出现在 GROUP BY 中;从 DISTINCT 改写过来时先把列补齐,否则直接报错或语义发生变化。
按某一列去重同时保留其他列的值,两者都做不到:DISTINCT 看的是整行组合,GROUP BY 看的是分组键。要保留某条代表记录就必须显式定义口径,例如取时间最新的一条。
两者都可以借助索引顺序省掉排序:去重或分组的列如果正好是某个索引的最左前缀,执行计划里就不会出现额外的排序或临时表。

易错点

  • 多列去重时两边列集合不一致:DISTINCT 改成 GROUP BY 时漏列,去重范围变了,行数比预期多
  • 以为 DISTINCT 能顺带统计:它只负责去重,需要计数必须换 GROUP BY
  • 改写后触发 ONLY_FULL_GROUP_BY 报错:非聚合列都要进 GROUP BY,或按库的规则显式处理
  • 以为 DISTINCT 一定比 GROUP BY 快:两者执行方式接近,都靠排序、临时表或借索引,性能要看实际计划
  • 对含 NULL 的列去重后做匹配:多个 NULL 被合并成一组,用于业务主键比对时容易错漏

数据来源与参考

  • MySQL 8.0 Reference Manual · DISTINCT Optimization
  • PostgreSQL Documentation · SELECT DISTINCT
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌