用 group by + having 找重复记录:先按重复字段(如 email)分组,再用 having count(*) > 1 筛出重复组;若需完整行,须用 in 子查询或窗口函数 row_number() 标记重复行。

怎么用 GROUP BY + HAVING 找出重复的记录
直接说结论:重复数据必须靠 GROUP BY 分组后,再用 HAVING 对分组统计结果做条件筛选。不能只写 WHERE,因为 WHERE 在分组前执行,无法访问聚合结果。
典型场景是查「邮箱重复的用户」「订单号重复的订单」这类业务问题。关键不是“去重”,而是“找出哪些值重复了、重复几次、对应哪些行”。
-
GROUP BY后字段必须是你要判断是否重复的列,比如email、product_id -
HAVING COUNT(*) > 1是最常用过滤条件,COUNT(*)统计每组行数 - 如果想看具体有哪些重复值,SELECT 后只能写
GROUP BY的列或聚合函数,不能写原始表的其他字段(如id或name),否则会报错或结果不可靠
想看到重复数据的完整行,该怎么连表查
上面的方法只能返回重复的值和次数,但业务上往往需要知道「是哪几条记录在重复」。这时得把聚合结果当子查询,再跟原表 JOIN 或用 IN 关联。
常见写法是用 IN 子查询:
SELECT * FROM users WHERE email IN ( SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1 );
注意点:
- 子查询里
SELECT email必须和外层WHERE email IN的字段一致,类型也要匹配 - 如果重复字段可能为
NULL,IN会忽略NULL,需额外处理,比如加OR email IS NULL并在子查询中补上HAVING COUNT(*) > 1 OR COUNT(email) != COUNT(*) - 性能上,大表慎用
IN子查询;可考虑改用EXISTS或窗口函数(见下一条)
用窗口函数 ROW_NUMBER() 更灵活地标记重复行
当需要区分“首次出现”和“后续重复”、或只保留一条而删掉其余时,ROW_NUMBER() 比 GROUP BY 更直接。
示例:给每个 email 分组内按 id 排序,标出行号
SELECT *,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn
FROM users;
这样就能轻松筛出重复行(rn > 1)或去重留一(rn = 1)。优势在于:
- 不用嵌套子查询,逻辑更线性
- 能保留所有原始字段,不需要额外
JOIN - 支持复杂排序逻辑,比如优先保留最新注册的用户(
ORDER BY created_at DESC) - 但注意:MySQL 8.0+、PostgreSQL、SQL Server 支持,老版本 MySQL(5.7 及之前)不支持窗口函数
HAVING 里能用哪些表达式?COUNT(*) 不是唯一选择
HAVING 的本质是过滤分组后的结果集,所以它能用任何聚合函数或组合表达式,不只是 COUNT(*)。
例如:
- 查「平均订单金额超过 500 且下单次数不少于 3 次的客户」:
HAVING AVG(amount) > 500 AND COUNT(*) >= 3 - 查「有多个不同收货地址的用户」:
HAVING COUNT(DISTINCT address) > 1 - 查「最早和最晚下单时间差超过一年的用户」:
HAVING MAX(order_time) - MIN(order_time) > INTERVAL '1 year'(PostgreSQL/MySQL 语法略有差异)
容易忽略的一点:HAVING 中不能用别名(如 SELECT COUNT(*) AS cnt ... HAVING cnt > 1 会报错),必须重复写表达式。










