where中不能使用count(),必须用group by配合having筛选分组结果;查重复值需select分组字段,查次数需显式写count(*) as cnt;查全部重复行可用子查询或窗口函数;去重删数据推荐row_number()配合order by确保稳定性。

WHERE 里不能用 COUNT(),这是最常踩的语法坑
直接写 WHERE COUNT(*) > 1 会报错 ERROR 1111 (HY000): Invalid use of group function。因为 COUNT() 是聚合函数,只能在分组后使用,而 WHERE 是行级过滤,在分组前就执行了。
正确路径只有一条:先 GROUP BY 分组,再用 HAVING 对分组结果做条件筛选。
-
HAVING必须紧跟GROUP BY,顺序不能颠倒 - 如果只想要重复值本身(比如邮箱列表),
SELECT后只需写分组字段,不用带COUNT(*) - 如果要同时看重复次数,就得显式写出
COUNT(*) AS cnt
查重复值 + 次数:用 GROUP BY + HAVING 最稳
这是兼容性最好、所有 SQL 方言都支持的方式。比如查 users 表中重复的 email 及其出现次数:
SELECT email, COUNT(*) AS cnt FROM users GROUP BY email HAVING COUNT(*) > 1;
注意点:
- 多字段重复判断时,
GROUP BY要写全,比如GROUP BY first_name, last_name -
HAVING COUNT(*) > 1中的*和email无关——它统计的是每组的行数,不是字段非空数 - MySQL 5.7 默认开启
ONLY_FULL_GROUP_BY,所以SELECT里不能出现未出现在GROUP BY中的非聚合字段
查所有重复的原始行(不止去重后的值)
上面的语句只返回每个重复值一行 + 计数,但排查时往往需要看到全部撞车的记录。这时得用子查询或窗口函数。
子查询方式(兼容 MySQL 5.6+、PostgreSQL、SQL Server):
SELECT * FROM users WHERE email IN ( SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1 );
窗口函数方式(MySQL 8.0+、PostgreSQL、SQL Server 支持)更直观:
SELECT * FROM ( SELECT *, COUNT(*) OVER (PARTITION BY email) AS cnt FROM users ) t WHERE cnt > 1;
区别在于:
- 子查询在老版本数据库上更可靠,但性能可能略差(尤其大表)
- 窗口函数一次扫描就能标出每行的重复计数,且能配合
ORDER BY id稳定排序 - 如果重复依据是多个字段,
PARTITION BY也得写全,比如PARTITION BY name, department
想标记“第几次出现”?用 ROW_NUMBER() 更灵活
当你要区分保留哪一条(比如留 id 最小的)、或者准备删掉多余行时,ROW_NUMBER() 比单纯计数更有用。
SELECT *, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn FROM users;
结果里 rn = 1 的是每组首行,rn > 1 的就是重复项。删数据时可直接:
DELETE FROM users
WHERE id IN (
SELECT id FROM (
SELECT id,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn
FROM users
) t
WHERE rn > 1
);
关键细节:
-
ORDER BY id不加的话,rn分配可能不稳定,不同执行结果不一致 - 部分旧版 MySQL(如 5.7)不支持窗口函数,必须用子查询 + 自连接模拟
-
PARTITION BY字段组合必须和业务定义的“重复逻辑”完全一致,少一个就漏判
真正容易被忽略的,是分组维度和业务语义是否对齐——比如用 email 判重,但实际要保留的是“最新注册用户”,那就得把 ORDER BY 换成 created_at DESC,而不是默认按 id。










