<p>不能直接 SELECT * 配合 GROUP BY,因为标准 SQL 要求 SELECT 列表字段必须出现在 GROUP BY 子句中或为聚合函数结果;否则报错,需先用 GROUP BY + HAVING 找出重复键,再通过子查询或 JOIN 回查完整行。</p>

查重复行时为什么不能直接 SELECT * 配合 GROUP BY
因为标准 SQL 要求 SELECT 列表里的字段,要么是 GROUP BY 子句中的列,要么是聚合函数(如 COUNT()、MAX())的结果。直接写 SELECT * 会报错,比如 PostgreSQL 报 column "xxx" must appear in the GROUP BY clause or be used in an aggregate function,MySQL 在严格模式下也一样。
所以得换思路:先用 GROUP BY 找出哪些“组合值”重复了,再把原表中匹配这些组合的完整行捞出来。
用 GROUP BY + HAVING 找出重复的分组键
这是第一步,确定哪些字段组合出现了多次。关键在 HAVING COUNT(*) > 1,它过滤的是分组后的结果,不是每行。
- 如果按单字段去重,比如查
email重复:SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1 - 如果按多字段联合判断重复(如姓名+手机号),就一起写进
GROUP BY:SELECT name, phone, COUNT(*) FROM users GROUP BY name, phone HAVING COUNT(*) > 1 - 注意
COUNT(*)统计的是每组行数,不是某列非空值数量;想排除 NULL 干扰,可加WHERE name IS NOT NULL AND phone IS NOT NULL
用子查询或 JOIN 把重复行的完整数据查出来
上面只拿到了重复的“键”,要看到原始数据行,得再套一层。推荐用子查询 + IN 或 JOIN,别用窗口函数除非确定数据库支持且性能可控。
- 子查询写法(兼容性最好):
SELECT * FROM users WHERE (name, phone) IN ( SELECT name, phone FROM users GROUP BY name, phone HAVING COUNT(*) > 1 );
- 注意:PostgreSQL 和 MySQL 8.0+ 支持行构造器
(a,b),但旧版 MySQL 不支持,得拆成两个IN或改用JOIN - JOIN 写法(更直观,但要注意笛卡尔风险):
SELECT u1.* FROM users u1 INNER JOIN ( SELECT name, phone FROM users GROUP BY name, phone HAVING COUNT(*) > 1 ) u2 ON u1.name = u2.name AND u1.phone = u2.phone;
查重复时容易忽略的 NULL 和性能问题
NULL 在 GROUP BY 中会被视为相同值,但不同数据库对 NULL = NULL 的处理有差异;另外大表不加索引会导致全表扫描,慢得明显。
- 如果业务上认为
NULL不算重复(比如空邮箱不应被归为同一组),得提前WHERE过滤掉:WHERE phone IS NOT NULL - 对用于分组的字段建联合索引能显著提速,例如:
CREATE INDEX idx_name_phone ON users(name, phone); - 如果表有几百万行,先用
EXPLAIN看执行计划,确认是否走了索引,避免HAVING在内存里做大量分组
真正难的不是写出语句,而是想清楚“重复”的定义——是整行完全一致?还是某些业务字段组合唯一?这个逻辑一旦定错,后面所有结果都不可信。

















