GROUP BY 配合 HAVING 才能筛出重复行;单纯 GROUP BY 仅分组,HAVING 对分组后 COUNT(*)>1 过滤,WHERE 中不可用聚合函数;查整行重复需对所有非主键列分组,或用窗口函数返回全部重复记录。

GROUP BY 配合 HAVING 才能筛出重复行
单纯 GROUP BY 只是分组,不会过滤重复;必须搭配 HAVING 对分组后的计数做条件判断。常见错误是写成 WHERE COUNT(*) > 1——COUNT() 是聚合函数,不能在 WHERE 中使用,否则直接报错:ERROR: aggregate functions are not allowed in WHERE。
正确做法是:先按目标字段 GROUP BY,再用 HAVING COUNT(*) > 1 留下出现次数大于 1 的组。
- 如果查「整行完全重复」,需对所有列(或业务上能唯一标识一行的列)一起
GROUP BY - 如果只关心某几列重复(如
email字段重复),就只对这几列GROUP BY -
HAVING在GROUP BY之后执行,作用对象是分组结果,不是原始行
查整行重复时,GROUP BY 要包含所有非聚合列
想找出表中真正一模一样的重复记录(比如两条完全相同的用户数据),得把所有字段都放进 GROUP BY。但手动列全字段易漏、难维护,更稳妥的方式是:用主键或唯一标识字段以外的所有字段分组。
例如表 users 有 id, name, email, created_at,其中 id 是主键,那重复行一定出现在 name, email, created_at 组合上:
SELECT name, email, created_at, COUNT(*) FROM users GROUP BY name, email, created_at HAVING COUNT(*) > 1;
注意:若表无主键,且字段含 NULL,GROUP BY 会把多个 NULL 视为同一组——这是 SQL 标准行为,不是 bug,但容易误判“重复”。
要返回全部重复记录(不止分组摘要),得用窗口函数或子查询
GROUP BY + HAVING 只返回每组一条摘要(如 name, COUNT(*)),但业务常需要看到所有重复的原始行(比如导出给运营人工核对)。这时不能只靠 GROUP BY,得嵌套或换方案:
- 用
COUNT(*) OVER (PARTITION BY ...)窗口函数,给每行打上重复次数标签,再外层WHERE cnt > 1 - 或用子查询:先用
GROUP BY + HAVING得到重复的字段组合,再JOIN原表捞出完整记录 - PostgreSQL/MySQL 8.0+ 支持 CTE,可读性更好;旧版 MySQL 只能靠派生表
示例(窗口函数法):
SELECT * FROM ( SELECT *, COUNT(*) OVER (PARTITION BY email) AS cnt FROM users ) t WHERE cnt > 1;
性能和索引影响不能忽略
GROUP BY 在大数据量下很吃性能,尤其没索引时会触发全表扫描 + 临时文件排序。重复检测类查询常用于数据清洗,但上线后频繁跑可能拖垮数据库。
- 确保
GROUP BY的字段上有联合索引,比如查email, status重复,建索引:CREATE INDEX idx_email_status ON users(email, status) -
HAVING COUNT(*) > 1无法利用索引跳过非重复行,所以索引主要加速分组过程本身,而非过滤 - 如果只是临时查重,加
LIMIT控制返回行数,避免前端卡死
真正麻烦的是跨多表、带 JOIN 的重复检测——这时候 GROUP BY 字段来源变复杂,NULL 处理、空值匹配逻辑、执行计划是否走索引,都得逐条验证。

















