GROUP BY + HAVING COUNT(*) > 1 是识别重复记录的标准解法;需先按判重字段分组,再用HAVING过滤,WHERE不能用于聚合条件,查完整重复行须结合窗口函数或子查询。

用 GROUP BY + HAVING 找重复记录
直接用 GROUP BY 搭配 HAVING COUNT(*) > 1 是最常用也最可靠的方式。它不依赖主键或唯一索引,只看字段组合是否重复。
常见错误是写成 WHERE COUNT(*) > 1 —— COUNT() 是聚合函数,不能在 WHERE 里用,必须用 HAVING。
- 先
GROUP BY你想判断重复的列(比如email或user_id, order_date) - 再用
HAVING COUNT(*) > 1过滤出至少出现两次的组 - 如果要查出所有重复行(不止分组摘要),得用子查询或窗口函数配合
查出全部重复行(不只是分组摘要)
上面的 GROUP BY + HAVING 只返回每组一条汇总结果。若需列出所有重复的原始记录(比如 3 条相同邮箱的用户全打出来),得借助窗口函数或自连接。
推荐用 COUNT(*) OVER (PARTITION BY ...):性能好、逻辑直白、兼容多数现代数据库(PostgreSQL / SQL Server / MySQL 8.0+ / Oracle)。
- 写法示例:
SELECT * FROM ( SELECT *, COUNT(*) OVER (PARTITION BY email) AS cnt FROM users ) t WHERE cnt > 1;
- 注意:MySQL 5.7 不支持窗口函数,此时只能用自连接或子查询,但性能差、易漏边角情况(如 NULL 值参与比较)
-
PARTITION BY列中含NULL时,多数数据库把所有NULL归为同一组;但个别引擎(如旧版 SQLite)可能不这样处理,需实测
WHERE 和 HAVING 的执行顺序容易搞混
WHERE 在分组前过滤行,HAVING 在分组后过滤组——这个顺序错不了,但实际写的时候常因逻辑绕而写反。
- 想筛“邮箱不为空且重复的用户”?
WHERE email IS NOT NULL必须放前面,否则HAVING阶段看到的组可能已包含空值干扰 - 想筛“注册时间在 2023 年之后的重复邮箱”?
WHERE created_at >= '2023-01-01'要先执行,再分组,否则会把早于 2023 的记录也算进计数 - 别试图在
HAVING里引用未出现在SELECT或GROUP BY中的非聚合字段,会报错(如HAVING name = 'Alice'在没GROUP BY name时非法)
NULL 值是否算作重复?不同数据库行为不一致
这是最容易被忽略的实际坑。标准 SQL 规定 NULL = NULL 为未知(UNKNOWN),但 GROUP BY 却把所有 NULL 归为同一组——这看似矛盾,却是事实。
- PostgreSQL / SQL Server / MySQL 默认把
NULL当作相同值分组,所以COUNT(*)会把多条NULLemail 算作一次重复 - 如果你业务上认为
NULL不代表相同(比如“未填邮箱”彼此无关),就得显式排除:WHERE email IS NOT NULL - 更严苛场景下,可用
COALESCE(email, '')把NULL转为空字符串再分组,但要注意空字符串本身也可能真实存在
实际跑之前,先确认你的数据库版本是否支持所需语法,尤其窗口函数和 NULL 分组行为——线上环境和本地测试结果有时不一致。

















