必须用 group by 分组后配合 having 筛选重复项,因 count() 是聚合函数不能用于 where;示例:select email, count() as cnt from users group by email having count() > 1。

用 GROUP BY + COUNT() 找出重复值和次数
直接在 WHERE 之后加 HAVING 是行不通的,因为 COUNT() 是聚合函数,不能出现在 WHERE 子句里。必须用 GROUP BY 分组后,再用 HAVING 筛选重复项。
常见错误是写成 WHERE COUNT(*) > 1,这会报错 ERROR 1111 (HY000): Invalid use of group function。
- 先按目标字段(比如
email)分组:GROUP BY email - 对每组计数:
COUNT(*) - 只保留出现次数大于 1 的组:
HAVING COUNT(*) > 1
示例(查用户表中重复邮箱及其出现次数):
SELECT email, COUNT(*) AS cnt FROM users GROUP BY email HAVING COUNT(*) > 1;
查出所有重复记录的完整行(不止字段值)
上面只返回去重后的字段+次数,但实际排查时往往需要看到具体哪几条记录撞了。这时得用子查询或窗口函数。
兼容性最广的做法是用子查询关联原表:
- 内层查出所有重复的
email值(用上一节的逻辑) - 外层用
IN拉出这些email对应的所有原始行
注意:如果重复依据是多个字段(如 first_name 和 last_name),子查询里的 GROUP BY 和 IN 都要改成元组形式,部分数据库(如 MySQL 8.0+、PostgreSQL)支持 (first_name, last_name) IN (...),老版本 MySQL 则需拼字符串或改用 JOIN。
示例(MySQL 8.0+):
SELECT * FROM users WHERE (email) IN ( SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1 );
用窗口函数 ROW_NUMBER() 标记重复行(推荐用于去重前分析)
当你要区分“首次出现”和“后续重复”、或者准备删掉多余行时,ROW_NUMBER() 比 COUNT() 更灵活。它能给每组内的行编号,便于定位。
关键点:
-
PARTITION BY定义重复判断维度(如PARTITION BY email) -
ORDER BY决定组内排序逻辑(建议加上主键,如ORDER BY id,保证结果稳定) - 编号为 1 的是“保留项”,>1 的是待清理的重复项
示例(标记所有重复邮箱中的非首行):
SELECT *,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn
FROM users
WHERE email IN (
SELECT email
FROM users
GROUP BY email
HAVING COUNT(*) > 1
);
性能与索引提醒
重复检测类查询容易慢,尤其在没索引的大表上。执行前务必确认相关字段有索引:
- 单字段重复:给该字段建普通索引,例如
CREATE INDEX idx_email ON users(email); - 多字段组合重复:建联合索引,顺序按
GROUP BY中字段顺序来,例如CREATE INDEX idx_name_full ON users(first_name, last_name);
另外,SELECT * 在大结果集下可能拖慢响应,只查必要字段;若只是检查是否存在重复,用 EXISTS + 子查询比拉全量更轻量。
真正难的不是写出语句,而是确认“重复”的业务定义是否覆盖了空值、大小写、前后空格等边界情况——这些往往得靠 COALESCE()、TRIM()、LOWER() 预处理,而且会影响索引能否命中。











