查重复值必须用GROUP BY+HAVING:先按字段分组,再用HAVING COUNT(*)>1筛选重复组;WHERE不能用聚合函数,否则报错;需查完整重复行时,须用窗口函数或子查询。

用 GROUP BY + HAVING 找重复值
直接筛选出现次数大于 1 的记录,核心是分组后过滤——GROUP BY 聚合,HAVING 做条件判断。不能用 WHERE,因为聚合函数(如 COUNT())不能在 WHERE 中使用。
常见错误是写成 WHERE COUNT(*) > 1,这会报错:ERROR: aggregate functions are not allowed in WHERE。
SELECT user_id, COUNT(*) FROM orders GROUP BY user_id HAVING COUNT(*) > 1- 如果想查整行数据(比如所有重复的订单记录),得用子查询或窗口函数,
GROUP BY本身只返回每组一条结果 -
HAVING是在分组后执行,WHERE是在分组前过滤——想先排除无效数据(如status IS NOT NULL),放WHERE;想按统计结果筛选,必须用HAVING
查出所有重复的原始行(不止分组摘要)
当需要看到每条重复记录(比如导出全部重复的用户注册信息),GROUP BY 不够用,得借助窗口函数 COUNT() OVER 或自连接。
推荐用窗口函数,简洁且可读性强:
SELECT * FROM ( SELECT *, COUNT(*) OVER (PARTITION BY email) AS cnt FROM users ) t WHERE cnt > 1;
-
PARTITION BY email相当于按email分组计数,但不折叠行,每行都带自己的计数值 - 注意:MySQL 8.0+、PostgreSQL、SQL Server 2012+ 支持窗口函数;老版本 MySQL(5.7 及以前)只能用自连接或子查询关联
- 如果字段组合重复(比如
(first_name, last_name)),PARTITION BY里写多个字段,用逗号分隔
性能和索引注意事项
查重复本身不慢,但数据量大时没索引会全表扫描,尤其 GROUP BY 或 PARTITION BY 的字段没索引,速度可能断崖式下降。
- 对用于判重的字段(如
email、phone)建索引能显著提升GROUP BY和窗口函数的执行效率 CREATE INDEX idx_users_email ON users(email);- 复合重复场景(如联合唯一约束失效),建议在业务层加
UNIQUE约束,而不是只依赖查询发现——查重复是补救,防重复才是根本 - 如果表有千万级数据,
HAVING COUNT(*) > 1可能比> 10慢很多,因为优化器难以提前剪枝
去重后保留最新/最旧的一条怎么搞?
查出重复只是第一步,实际常要“留一条删其余”。这时候不能只靠 COUNT,得结合 ROW_NUMBER() 或 MIN/MAX(id)。
例如保留每个 email 下 id 最大的那条:
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id DESC) AS rn FROM users ) t WHERE rn = 1;
-
ORDER BY id DESC决定谁被留下;换成ASC就留最早插入的 - SQLite 不支持窗口函数,得用相关子查询,性能差不少;Oracle 旧版要用
ROWNUM套嵌套,写法更绕 - 执行删除前,务必先用
SELECT验证结果——误删没有后悔药
查重复看似简单,但真正在生产环境跑,最容易卡在没索引、窗口函数不支持、或者删错数据。别跳过验证步骤,也别默认 COUNT(*) > 1 就是最终答案。










