where中不能使用count(*)>1,因聚合函数只能在having中使用;查重复行需用子查询或窗口函数,注意null处理、partition by字段准确性及row_number()排序稳定性。

WHERE 里写 COUNT(*) > 1 一定报错
数据库执行顺序是 WHERE → GROUP BY → HAVING,COUNT(*) 在分组前根本不存在,所以 WHERE COUNT(*) > 1 会直接报错,典型错误信息是:ERROR: aggregate functions are not allowed in WHERE 或 Invalid use of aggregate function。必须用 HAVING 替代,它专为过滤分组结果而设。
GROUP BY + HAVING 只能返回分组摘要,不是原始重复行
这个组合本身只输出每组一条记录(比如 email 和计数),但排查问题时你需要看到所有重复的完整行。这时候不能只靠 HAVING,得配合其他手段:
- 兼容性最广的做法:用子查询先找出重复值,再用
IN关联原表,例如SELECT * FROM users WHERE email IN (SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1) - 性能更好、一次扫描的写法(MySQL 8.0+ / PostgreSQL / SQL Server):用窗口函数
COUNT(*) OVER (PARTITION BY email),再外层筛选cnt > 1 - 注意 NULL:所有主流数据库默认把
NULL归为同一组,若业务上认为空值不构成重复,必须提前加WHERE email IS NOT NULL
PARTITION BY 字段必须和业务定义完全一致
重复的判定逻辑由你定义,数据库只是忠实执行。比如你认为“同一用户同一天订单重复”才算异常,那 PARTITION BY 就必须写成 PARTITION BY user_id, order_date:
- 漏掉
order_date,只按user_id分组 → 把不同日期的订单也标为重复 - 多写了无关字段(比如
created_at)→ 每行都变成唯一组,永远查不到重复 - 字段顺序不影响结果,但建议和业务文档/校验逻辑保持一致,避免后续维护混淆
查出来之后别急着删,先核对 cnt 和 rn 的区别
COUNT(*) OVER (PARTITION BY ...) 给出的是“这组总共几条”,ROW_NUMBER() OVER (PARTITION BY ... ORDER BY id) 给出的是“这条在组里排第几”。两者用途完全不同:
- 用
cnt > 1筛出所有属于重复组的行(适合人工核对) - 用
rn > 1才表示“该删的冗余行”(保留rn = 1的那条) - ORDER BY 必须明确指定,否则同组内排序不稳定,每次执行结果可能不同
真正容易被忽略的是:没主键或没唯一标识的表,ROW_NUMBER() 的 ORDER BY 如果只写 ORDER BY email,会导致相同邮箱的行排序不可控——必须带一个能区分行的字段,比如 id 或 created_at。











