在数据库操作中,查找表中的重复记录是开发和维护过程中经常遇到的需求,比如需要排查用户表中重复注册的同手机号账号、订单表中重复提交的同商品订单等场景,都可以通过SQL的GROUP BY和HAVING子句配合实现高效查询。

GROUP BY与HAVING的基础作用
GROUP BY是SQL中用于分组的关键字,它会按照指定的字段将表中的数据分成多个组,相同字段值的行会被归到同一个组里。而HAVING子句则是对分组后的结果进行筛选,作用和WHERE类似,但WHERE是在分组前筛选行,HAVING是在分组后筛选组。
要查找重复记录,核心逻辑是先通过GROUP BY按照可能出现重复的字段分组,再用COUNT()函数统计每个组内的记录数,最后用HAVING筛选出计数大于1的组,这些组对应的就是存在重复的记录。
查询单字段重复记录
假设我们有一张用户表user_info,结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int | 用户ID,主键 |
| user_name | varchar | 用户名 |
| phone | varchar | 手机号 |
| register_time | datetime | 注册时间 |
现在需要查找手机号重复的所有用户记录,首先可以先统计每个手机号出现的次数:
-- 统计每个手机号的出现次数 SELECT phone, COUNT(*) AS repeat_count FROM user_info GROUP BY phone
上述语句会返回每个手机号以及对应的记录数,接下来加上HAVING条件筛选出重复的记录:
-- 查找手机号重复的分组 SELECT phone, COUNT(*) AS repeat_count FROM user_info GROUP BY phone HAVING COUNT(*) > 1
如果我们需要获取所有重复手机号对应的完整用户记录,可以将上述查询结果作为子查询,和原表做关联:
-- 获取所有手机号重复的用户完整记录
SELECT u.*
FROM user_info u
JOIN (
SELECT phone
FROM user_info
GROUP BY phone
HAVING COUNT(*) > 1
) t ON u.phone = t.phone
ORDER BY u.phone, u.register_time
查询多字段重复记录
有时候重复的判断需要基于多个字段,比如用户表中用户名和手机号都相同才认为是重复记录,这时候只需要在GROUP BY后面加上多个字段即可:
-- 查找用户名和手机号都重复的分组 SELECT user_name, phone, COUNT(*) AS repeat_count FROM user_info GROUP BY user_name, phone HAVING COUNT(*) > 1
同样,如果需要获取多字段重复对应的完整记录,也可以用子查询关联的方式实现:
-- 获取用户名和手机号都重复的用户完整记录
SELECT u.*
FROM user_info u
JOIN (
SELECT user_name, phone
FROM user_info
GROUP BY user_name, phone
HAVING COUNT(*) > 1
) t ON u.user_name = t.user_name AND u.phone = t.phone
ORDER BY u.user_name, u.phone, u.register_time
注意事项
- GROUP BY后面的字段需要和SELECT中除了聚合函数之外的字段保持一致,否则部分数据库会报错。
- HAVING后面可以使用聚合函数,而WHERE后面不能直接使用聚合函数,这是两者的重要区别。
- 如果表数据量较大,建议在分组使用的字段上建立索引,提升查询效率。
- 如果需要删除重复记录只保留一条,可以在查询到重复记录后,结合窗口函数或者子查询实现,避免误删数据。
实际业务中查询重复记录前,建议先确认重复的判断规则,避免因为规则错误导致查询结果不符合预期。