直接用group by+having count()>1可快速识别重复键,但无法返回原始重复行;要获取完整重复记录,必须通过子查询或窗口函数回查,因select 与group by混用违反sql标准且结果不可靠。

直接用 GROUP BY 本身不能返回重复的“数据行”,它只能返回分组摘要;要拿到原始重复行,必须配合 HAVING COUNT(*) > 1 先筛出重复键,再通过子查询或窗口函数回查。
为什么 SELECT * + GROUP BY 会报错?
标准 SQL 要求 SELECT 列表中的非聚合字段必须全部出现在 GROUP BY 子句中。写 SELECT *, COUNT(*) FROM t GROUP BY a 会触发类似 column "b" must appear in the GROUP BY clause 的错误。
- MySQL 5.7+ 严格模式、PostgreSQL、SQL Server 都强制此规则
- 即使 MySQL 旧版允许,结果也不可靠——其他列值来自哪一行是未定义的
- 真正想查的是“哪些完整行在指定字段上重复”,不是“每组挑一行出来”
用 HAVING COUNT(*) > 1 筛重复键的正确写法
HAVING 是唯一能过滤分组后聚合结果的子句,WHERE 在分组前执行,无法使用 COUNT(*)。
- 查单列重复(如邮箱):
SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1 - 查多列组合重复(如姓名+手机号):
SELECT name, phone, COUNT(*) FROM users GROUP BY name, phone HAVING COUNT(*) > 1 - 别名不能用于
HAVING条件,HAVING cnt > 1大多数数据库不支持,得写HAVING COUNT(*) > 1 -
COUNT(*)比COUNT(email)更安全——后者跳过NULL,可能漏掉含空值的重复组
如何拿到所有重复的原始数据行?
上面的语句只返回分组键和计数,业务排查需要看到每条重复记录本身。
- 兼容性最好:用子查询 +
IN(注意多字段需数据库支持行构造器):SELECT * FROM users WHERE (name, phone) IN (SELECT name, phone FROM users GROUP BY name, phone HAVING COUNT(*) > 1) - 旧版 MySQL 不支持
(a,b)行构造器,改用EXISTS或两层IN(name IN (...) AND phone IN (...)不等价,慎用) - 推荐窗口函数(MySQL 8.0+/PostgreSQL/SQL Server):
SELECT * FROM (SELECT *, COUNT(*) OVER (PARTITION BY name, phone) AS cnt FROM users) t WHERE t.cnt > 1 -
NULL值会被GROUP BY当作相同值归为一组,若业务中不认为NULL算重复,提前加WHERE name IS NOT NULL AND phone IS NOT NULL
最容易被忽略的是 NULL 参与分组的行为,以及 HAVING 和 WHERE 的执行时机差异——这两个点一旦搞错,查出来的就不是你要的“重复行”。











