查重复数据必须用 group by + having;因重复是跨行比较结果,需先按字段分组再统计行数,用 having 筛选 count(*) > 1 的组,where 中不可用聚合函数。

查重复数据必须用 GROUP BY + HAVING
直接 SELECT * 加 WHERE 无法判断“重复”,因为重复是跨行比较的结果。SQL 中识别重复的唯一可靠路径是:按待检测字段分组,再统计每组行数,最后筛选出数量大于 1 的组。
常见错误是试图用 WHERE COUNT(*) > 1 —— 这会报错,因为 COUNT() 是聚合函数,不能出现在未分组的 WHERE 子句里。
- 必须先写
GROUP BY 字段名(可以是单列,也可以是多列,如GROUP BY user_id, status) - 过滤重复组要用
HAVING COUNT(*) > 1,不是WHERE - 如果还想看原始记录,得用子查询或窗口函数,不能只靠
GROUP BY返回明细行
查重复值及次数的标准写法
最常用、兼容性最好的写法是返回重复的字段组合及其出现次数:
SELECT name, email, COUNT(*) AS cnt FROM users GROUP BY name, email HAVING COUNT(*) > 1;
注意点:
-
COUNT(*)比COUNT(字段)更稳妥,后者会忽略该字段为NULL的行,可能导致漏判 - 如果想查所有字段都相同的重复行,
GROUP BY后要列出所有字段(但表字段多时很麻烦,此时建议改用窗口函数) - MySQL 5.7+ 默认开启
ONLY_FULL_GROUP_BY,所以SELECT *+GROUP BY id会报错,别硬套旧写法
要返回全部重复记录(不止分组摘要)
当需要看到每一条重复的原始数据(比如删掉其中几条),就得把重复组“展开”。推荐用窗口函数 COUNT() OVER,它不改变行数:
SELECT id, name, email,
COUNT(*) OVER (PARTITION BY name, email) AS cnt
FROM users
WHERE COUNT(*) OVER (PARTITION BY name, email) > 1;
但注意:上面写法在 MySQL 8.0+、PostgreSQL、SQL Server 2012+ 支持;SQLite 和旧版 MySQL 不支持窗口函数,得用自连接:
- 自连接方案性能差、语法绕:对
users a和users b做JOIN,条件是关键字段相等且a.id != b.id - 容易误删——比如三行完全相同,自连接会产生 6 行结果,需配合
DISTINCT或ROW_NUMBER() - 窗口函数方案更直观,也更容易加
ORDER BY或后续处理
去重前务必确认业务语义和 NULL 处理
重复判定看似简单,实际最容易翻车的是 NULL:SQL 中 NULL = NULL 为 FALSE,所以默认的 GROUP BY 会把所有 NULL 值归为一组——这有时符合预期,有时不是。
- 如果字段允许
NULL且你认为NULL不代表相同含义(例如 email 为空 ≠ 邮箱相同),得提前用COALESCE(email, '')统一转换 - 大小写敏感性取决于字段 collation,
'Alice'和'alice'在utf8mb4_0900_as_cs下算不同值,在_ci结尾的排序规则下算相同 - 字符串末尾空格是否忽略,也由 collation 决定,不是所有数据库都像 MySQL 那样自动 trim
真正难的从来不是写出语句,而是搞清“什么才算重复”——这个定义必须来自业务,而不是数据库默认行为。











