目录

题目描述

182. 查找重复的电子邮箱

题意分析

Person(id, email)id 唯一,但同一个 email 可能出现在多行。要求返回所有出现次数大于 1 的邮箱,每个重复邮箱只输出一次;题目保证 email 不为 NULL,并允许任意输出顺序。

输出粒度是“每个邮箱一行”,因此先按 email 分组。分组之后每组对应一个邮箱,COUNT(*) 就是该邮箱的出现次数,再用 HAVING COUNT(*) > 1 保留重复组。

WHERE 在分组前过滤原始行,不能直接引用聚合结果;HAVING 在分组后过滤组,这正是两者最核心的区别。

样例 a@b.com, c@d.com, a@b.com 分成两个组:a@b.com 的计数为 2,c@d.com 的计数为 1,因此只输出前者。

解法:SQL 分组统计

核心思路

GROUP BY email 把所有相同邮箱折叠成一个逻辑组。聚合后的不变量是:结果流中的每一行代表一个唯一邮箱,并携带该邮箱在原表中的完整行数

HAVING COUNT(*) > 1 只保留计数超过 1 的组。最终投影邮箱列即可,不需要额外 DISTINCT:分组本身已经保证每个邮箱只出现一行。

使用显式列名 GROUP BY emailGROUP BY 1 更清楚,也不会因调整 SELECT 列顺序而改变含义。

解题步骤

  • Person 读取所有记录。
  • email 分组,使相同邮箱进入同一组。
  • 对每组执行 COUNT(*)
  • HAVING 阶段保留计数大于 1 的组。
  • 输出邮箱并命名为题目要求的 Email

代码实现

SELECT email AS Email
FROM Person
GROUP BY email
HAVING COUNT(*) > 1;

复杂度分析

设表中有 $N$ 行、不同邮箱有 $U$ 个。实际代价由数据库执行计划决定:

  • 哈希聚合平均需要 $O(N)$ 时间和 $O(U)$ 临时空间。
  • 排序聚合需要 $O(N \log N)$ 时间;排序可能使用 $O(N)$ 内存,也可能在数据量大时落盘。
  • 若存在按 email 排序的可用索引,数据库可按索引顺序流式分组,扫描部分接近 $O(N)$,临时空间也会显著降低。

关键点总结

  • “找重复值”的通用 SQL 骨架是 GROUP BY key HAVING COUNT(*) > 1
  • WHERE 过滤行,HAVING 过滤聚合后的组;聚合条件必须放在后者。
  • GROUP BY 已经完成去重,外层再加 DISTINCT 没有必要。
  • COUNT(*) 表示组内行数,最符合题意。这里 COUNT(email) 也因题目保证非空而等价,但换到可空列时两者会不同。
  • 面试追问“大小写是否视为同一邮箱”时,应先确认数据库排序规则;若要求显式忽略大小写,可按 LOWER(email) 分组并统一输出规范。

易错点总结

  • 写成 WHERE COUNT(*) > 1WHERE 执行时分组和计数尚未产生,SQL 会报聚合函数使用位置错误。
  • id 分组id 是主键,每组永远只有一行,COUNT(*) > 1 永远不成立。
  • 只写 SELECT DISTINCT email:它只能去重,无法区分“原来出现一次”和“原来出现多次”,会把所有邮箱都输出。
  • 使用 HAVING COUNT(*) >= 1:所有非空组都满足,样例中的 c@d.com 会被错误输出;重复必须严格大于 1。
  • GROUP BY 1 依赖列位置:当前查询可能正确,但加入计数列或调整投影顺序后容易悄悄改变分组目标,面试代码应写清列名。
  • 默认大小写规则:不同数据库或列排序规则可能让 A@b.coma@b.com 分到同组或不同组;本题已保证小写,扩展场景必须明确归一化规则。

相似题目

题目 难度 考察点
196. 删除重复的电子邮箱 简单 不只识别重复,还要按最小 ID 保留一行并删除其余行
596. 超过 5 名学生的课 简单 GROUP BY + HAVING,但需按学生去重后计数
586. 订单最多的客户 简单 分组计数后再按计数排序取最大值
619. 只出现一次的最大数字 简单 HAVING COUNT(*) = 1 筛唯一组,再求最大值