group by必须包含所有判定重复的字段,否则分组错误导致误判;having count(*)>1用于筛选真正重复的组;null值需显式处理,group by结果不能直接删除,须结合子查询或窗口函数实现安全去重。

GROUP BY 必须包含所有判定重复的字段
GROUP BY 本身不“产生”重复,它只是把相同值的行归到一组。你看到的“重复”,其实是 SELECT 中混入了未分组的字段,导致数据库随机返回某一行的值——看着像重复,实则是不确定行为。真正要确认哪些行算重复,得先明确业务逻辑:是 email 相同就算重复?还是 name 和 phone 都相同才算?这些字段必须全部写进 GROUP BY 子句,缺一个,分组就错。
常见错误是只写 GROUP BY email,但表里存在多个不同 name 却同 email 的记录,结果被强行合并,后续删数据时会误伤正常行。正确做法是:GROUP BY email, name, phone(按实际去重维度列全)。
用 HAVING 筛出真正重复的组
HAVING COUNT(*) > 1 是确认“哪些组确实有重复”的关键条件。它作用于分组后结果,不是过滤单行。比如想查出所有重复的邮箱组合:
SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1;
这个查询不会删数据,但它告诉你:email 哪些值出现了多次、各出现几次。执行前务必跑一遍,确认结果集大小和预期一致——比如你预估有 12 条重复,结果返回 0 行,说明分组字段没写对或数据本就不重复。
注意:HAVING 不能替代表级去重逻辑。它只是定位工具,不是删除指令。别指望加个 HAVING 就能自动删掉重复行。
NULL 值会让 GROUP BY 分组失效
GROUP BY 把所有 NULL 当作相同值处理。如果 email 字段允许为空,且你没显式排除,那所有 email IS NULL 的行会被归为同一组——哪怕它们其他字段完全不同。这时 MIN(id) 可能只返回一个 ID,其余全被删掉。
解决办法只有两个:
- 加
WHERE email IS NOT NULL过滤掉空值再分组 - 改用
EXISTS+ 自连接方式,对NULL更鲁棒(尤其在 Oracle 或 PostgreSQL 中)
别依赖“看起来没问题”的结果。只要字段可能为空,就必须显式处理,否则删完才发现丢了几十条有效记录,回滚都难。
GROUP BY 结果不能直接用于 DELETE
GROUP BY 是查询语句,它输出的是聚合后的结果集,不是原始行的引用。你不能写 DELETE FROM users GROUP BY email——语法直接报错。它只能帮你定位重复组,真正删数据还得靠子查询、CTE 或自连接。
典型配合方式是:
- 先用
GROUP BY ... HAVING COUNT(*) > 1查出重复键值 - 再用这些键值去关联原表,结合
MIN(id)或ROW_NUMBER()找出要留的行 - 最后用
NOT IN、EXISTS或JOIN实施删除
最容易忽略的一点:不同数据库对子查询中引用原表的限制不同。MySQL 要求嵌套一层别名,PostgreSQL 允许直接用,SQL Server 对 NOT IN 含 NULL 极其敏感——这些细节不提前验证,删着删着就停在半路了。










