← 返回题目列表

SQL 中 DISTINCT 和 GROUP BY 有什么区别?

高频 中等 第 11 / 28 题 更新于 2026/07/29
SQLDISTINCTGROUP BY去重

简化版

DISTINCT 用来对查询结果去重,GROUP BY 用来按分组键聚合;只想去重优先看 DISTINCT,需要 COUNT/SUM/MAX 这类汇总时使用 GROUP BY

详细版

DISTINCT 作用在 SELECT 结果列上,它会把结果集中完全相同的行合并成一行。比如 SELECT DISTINCT city FROM users 表示拿到不重复的城市。

GROUP BY 会把表中的行按一个或多个字段分成若干组,然后每组输出一行,通常配合聚合函数使用。比如 SELECT city, COUNT(*) FROM users GROUP BY city 表示统计每个城市有多少用户。

二者在某些简单场景中结果相同,例如 SELECT DISTINCT city FROM usersSELECT city FROM users GROUP BY city 都能拿到城市列表。但语义不同:前者表达“结果去重”,后者表达“按城市分组”。面试中应优先强调语义,再补充执行计划可能相近,具体优化取决于数据库实现、索引和数据分布。

完整版教学

一、先把两个语义拆开

DISTINCT 的核心是“结果集去重”。数据库先根据查询条件得到候选行,再看 SELECT 输出的列组合是否重复,重复的组合只保留一份。

GROUP BY 的核心是“分桶聚合”。它先把行按分组键放入不同组,再对每个组计算聚合函数,最后每个组返回一行。

用同一张表看差异更直观:

-- users
-- id | city | age
--  1 | 北京 | 20
--  2 | 北京 | 21
--  3 | 上海 | 20

SELECT DISTINCT city FROM users;

SELECT city, COUNT(*) AS user_count
FROM users
GROUP BY city;

第一个查询只关心城市是否重复,第二个查询关心每个城市背后有多少行。

记忆钩子:DISTINCT 看“输出行是否重复”,GROUP BY 看“原始行应该分到哪个组”。

二、为什么有时结果看起来一样

当查询只输出分组键本身,并且没有聚合函数时,GROUP BY 的输出天然也是每个分组一行。

例如下面两条 SQL 在结果上可能一致:

SELECT DISTINCT city FROM users;

SELECT city
FROM users
GROUP BY city;

如果原表有 10000 行,城市只有 20 个,两个查询都可能返回 20 行。但这不代表它们完全等价,尤其当你再增加列、聚合函数、排序、过滤时,语义差异会放大。

写法主要目的常见搭配面试表达
DISTINCT去掉重复结果普通查询列输出唯一值
GROUP BY分组后计算COUNT/SUM/MAX/MIN/AVG每组一行
GROUP BY 无聚合可实现去重分组键可读性通常弱于 DISTINCT

三、多列去重时要看“列组合”

DISTINCT 不是只对第一列去重,而是对整个输出列组合去重。

SELECT DISTINCT city, age
FROM users;

如果数据是:

北京,20
北京,21
北京,20
上海,20

结果会保留 3 行:北京,20北京,21上海,20。这就是很多人误以为“按 city 去重”却多出行数的原因。

如果你真正想要“每个城市只保留一行,并且带上某个用户年龄”,就不能随便 DISTINCT city, age,而要定义保留规则,例如最小年龄、最大年龄或最新一条记录。

SELECT city, MIN(age) AS min_age
FROM users
GROUP BY city;

四、GROUP BY 的选择列规则更严格

标准 SQL 中,SELECT 里出现的非聚合列通常必须在 GROUP BY 中出现,否则数据库不知道每组多行里该取哪一个值。

-- 推荐:每个非聚合列都能解释来源
SELECT city, COUNT(*) AS user_count
FROM users
GROUP BY city;

-- 风险:name 不是分组键,也不是聚合结果
SELECT city, name, COUNT(*)
FROM users
GROUP BY city;

第二条 SQL 的问题是:北京这一组可能有 2 个用户,name 应该取谁?有些数据库会直接报错,有些数据库在特定模式下允许但结果不可靠。

执行逻辑可以这样理解:

原始行 -> 按 city 分组 -> 每组计算 COUNT(*) -> 输出 city + count

这个流程里没有一步能自然产生“任意 name 但仍然正确”的含义。

五、性能不能只靠关键字判断

面试中常见追问是:DISTINCTGROUP BY 谁更快?更好的回答是:它们可能走相似的去重/分组算法,性能取决于索引、排序、哈希聚合、数据量和输出列宽。

数据库实现上常见两类路径:

排序去重:读取 -> 按 key 排序 -> 相邻重复合并
哈希去重:读取 -> key 放入 hash 表 -> 已存在则跳过

假设 100 万行、城市 20 个,如果有 (city) 索引,数据库可能沿索引顺序快速得到唯一城市。如果输出列很多且没有合适索引,DISTINCT 需要比较更宽的列组合,成本就会上升。

因此写 SQL 时先保证语义准确,再用 EXPLAIN 看执行计划,而不是机械背“某个关键字一定更快”。

六、真实业务里怎么选

只拿唯一值列表时,优先用 DISTINCT,因为它把意图表达得最清楚。

SELECT DISTINCT department_id
FROM employees
WHERE status = 'active';

需要每组指标时,用 GROUP BY

SELECT department_id, COUNT(*) AS active_count
FROM employees
WHERE status = 'active'
GROUP BY department_id;

需要“每组取一条完整记录”时,不要用 DISTINCT 碰运气,应该用窗口函数或明确聚合规则。

SELECT *
FROM (
  SELECT e.*,
         ROW_NUMBER() OVER (
           PARTITION BY department_id
           ORDER BY hired_at DESC, id DESC
         ) AS rn
  FROM employees e
) t
WHERE rn = 1;

七、常见误区与追问

  • 误区:DISTINCT city, age 表示只按 city 去重。 它按输出列组合去重,age 不同就会保留多行。
  • 误区:GROUP BY 一定比 DISTINCT 快。 两者可能使用类似算法,必须结合索引和执行计划判断。
  • 误区:没有聚合函数也应该用 GROUP BY 去重。 可以做到,但表达“去重”时 DISTINCT 通常更直观。
  • 误区:分组后随便选择非分组列也没问题。 多行合成一组后,非分组列必须有明确来源,否则结果不稳定或直接报错。
  • 追问:如何每组取最新一条? 常用 ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...),然后取 rn = 1
  • 追问:多列唯一值列表怎么写?SELECT DISTINCT col1, col2 ...,它返回唯一的列组合。

八、加强记忆

记住两条线:DISTINCT 是站在“最终输出行”的角度去重,GROUP BY 是站在“原始数据分组”的角度汇总。看到“唯一列表”想到 DISTINCT,看到“每类多少、每类最大、每类平均”想到 GROUP BY,看到“每组保留一条完整记录”再进一步想到窗口函数。