最常用可靠的查重方式是GROUP BY分组+HAVING COUNT(*)>1,支持所有主流数据库;可扩展为子查询关联原表查完整记录、多字段联合分组、窗口函数精准去重。
直接用 HAVING COUNT(*) > 1 就能揪出重复值
这是最常用也最可靠的查重方式,核心就是靠 group by 分组 + having 过滤。它不依赖主键、不要求数据库版本,mysql、postgresql、sql server 全都支持。
- 先按目标字段分组:
GROUP BY email,把相同邮箱的行归到一组 - 再筛出组内不止一条的:
HAVING COUNT(*) > 1 - 如果只关心“哪些值重复了”,这条就够了:
SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1 - 注意:
HAVING是对分组结果过滤,不能写成WHERE COUNT(*) > 1——那会报错Error Code: 1111 (Invalid use of group function)
想看完整重复记录?别只查字段,得连表捞数据
上面那条只能告诉你“a@b.com 重复了 3 次”,但你不知道是哪三条记录。这时候需要把重复值拿回去做匹配。
- 用子查询先拿到重复的值:
(SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1) - 再用
JOIN或IN关联回原表:SELECT * FROM users WHERE email IN (子查询) - 更稳妥的做法是
JOIN,尤其当字段可能为NULL时:IN对NULL不生效,JOIN能正确包含(但要注意ON条件是否允许空值) - 如果表很大,记得在
email字段建索引:CREATE INDEX idx_email ON users(email),否则JOIN可能慢到超时
多字段组合重复?GROUP BY 后面跟多个列就行
比如查“同名同姓又同生日”的员工,或者“同一订单里重复添加了同一个商品规格”,就得靠多字段联合分组。
- 语法很简单:
GROUP BY first_name, last_name, birth_date - 顺序无关紧要,但所有字段必须同时出现在
SELECT列表里(除非用了聚合函数),否则 MySQL 5.7+ 严格模式会报错Error Code: 1055 - 字段越多,分组粒度越细,结果越“精准”但也越容易漏掉逻辑重复(比如姓名拼写差异、日期格式不统一),查前先清洗或标准化更好
- 示例:
SELECT order_id, product_id, spec_code, COUNT(*) FROM order_items GROUP BY order_id, product_id, spec_code HAVING COUNT(*) > 1
MySQL 8.0+ 用窗口函数更灵活,但别滥用 ROW_NUMBER()
如果你不仅要找重复,还要区分“第几条是重复的”“要不要保留最早那条”,ROW_NUMBER() 是更可控的选择。
- 典型写法:
SELECT * FROM (SELECT *, ROW_NUMBER() OVER(PARTITION BY email ORDER BY id) AS rn FROM users) t WHERE rn > 1 -
PARTITION BY定义重复组,ORDER BY id决定谁排第一(也就是你想保留的那条) - 性能上,窗口函数比多次
JOIN更稳定,但全表扫描开销不小;大表务必确保email和id有联合索引 - 陷阱:别写成
OVER(PARTITION BY email)就完事——没ORDER BY的话,ROW_NUMBER()排序不可控,保留哪条是随机的
查重看着简单,真正上线时最容易翻车的是字段隐式类型转换(比如字符串带空格、大小写混用)、NULL 值处理逻辑不一致,还有没加索引导致查询卡死。动手前先 EXPLAIN 看一眼执行计划。

















