最稳做法是 SELECT 字段, COUNT() FROM 表 GROUP BY 字段 HAVING COUNT()>1;聚合函数不可用于 WHERE,必须用 HAVING 筛选;多字段重复需在 GROUP BY 中包含所有字段;NULL 值会被归为一组,需 WHERE 排除空值再统计。

怎么用 GROUP BY + COUNT 找出重复值
直接在 GROUP BY 字段后加 COUNT(*),再用 HAVING 筛出数量大于 1 的组。这是最稳、最通用的做法,所有主流 SQL 数据库都支持。
常见错误是把 COUNT(*) 放在 WHERE 里——不行,聚合函数不能进 WHERE,必须用 HAVING。
SELECT name, COUNT(*) FROM users GROUP BY name HAVING COUNT(*) > 1- 想看完整记录?得用子查询或窗口函数,
GROUP BY本身只返回每组一条结果 - 多个字段判重?比如“姓名 + 手机号”组合重复:
GROUP BY name, phone,别漏掉任一字段
为什么不能只用 DISTINCT 或 EXISTS
DISTINCT 只去重,不告诉你哪条重复、重复几次;EXISTS 子查询写起来绕,性能通常不如 GROUP BY + HAVING 直接。
尤其当表有几百万行时,GROUP BY 能利用索引(如果分组字段有索引),而自关联的 EXISTS 很容易触发全表扫描。
- 复合索引有用:
CREATE INDEX idx_name_phone ON users(name, phone)能加速GROUP BY name, phone - MySQL 8.0+ 和 PostgreSQL 支持
COUNT(*) OVER (PARTITION BY ……),但会多返回冗余行,不适合“只看重复项”场景
遇到 NULL 值时 COUNT 和 GROUP BY 怎么算
NULL 在 GROUP BY 中被视为相同值:所有 NULL 会归到同一组;COUNT(*) 会统计这组里的行数,但 COUNT(列名) 会忽略该列的 NULL。
所以如果你查 email 是否重复,而很多记录 email IS NULL,那 GROUP BY email 可能返回一行 NULL, 500——这不是业务意义上的“重复邮箱”,而是 500 个空值被合并了。
- 排除空值再查重复:
WHERE email IS NOT NULL GROUP BY email HAVING COUNT(*) > 1 - 想单独看空值有多少条?
SELECT COUNT(*) FROM users WHERE email IS NULL - PostgreSQL 里可用
GROUP BY email IS NULL, email把 NULL 和非 NULL 拆开,但多数情况没必要
性能差、卡住?先看执行计划和索引
没索引的 GROUP BY 在大表上可能慢得像卡死,特别是文本字段(如 TEXT 类型)参与分组时。
执行 EXPLAIN(MySQL)或 EXPLAIN ANALYZE(PostgreSQL)看有没有用上索引、是否出现 Using temporary; Using filesort。
- 避免对函数结果分组:
GROUP BY UPPER(name)通常无法走索引,先建函数索引或改用生成列 - 小表(
- SQL Server 用户注意:
ANSI_NULLS OFF会影响NULL分组行为,生产环境建议保持默认ON