必须用having不能用where,因为count()是聚合函数,where在group by前执行,此时分组尚未形成、计数结果不存在;正确写法是group by后接having count()>1,并注意null处理、索引优化及原始行需join或子查询回查。

为什么不能用 WHERE COUNT(*) > 1
直接写 WHERE COUNT(*) > 1 会报错:ERROR: aggregate functions are not allowed in WHERE。因为 COUNT(*) 是聚合函数,必须在分组后才能计算;而 WHERE 执行在 GROUP BY 之前,此时还没形成“组”,自然没有“组内计数”可言。
数据库执行顺序固定为:WHERE → GROUP BY → HAVING → ORDER BY。想筛组内行数,唯一合法位置是 HAVING。
GROUP BY + HAVING COUNT(*) > 1 的标准写法
查某列(如 email)重复出现的数据,语句结构非常固定:
SELECT email, COUNT(*) AS cnt FROM users GROUP BY email HAVING COUNT(*) > 1;
-
GROUP BY email是必需的——没它,HAVING就无组可筛 -
SELECT列表里只能出现GROUP BY字段(如email)或聚合表达式(如COUNT(*)),否则 MySQL 5.7+ 或 PostgreSQL 会报错 - 如果查多字段组合重复(如
user_id和order_date),GROUP BY必须写全:GROUP BY user_id, order_date
怎么拿到所有重复的原始行,不只是摘要
GROUP BY + HAVING 只返回每组一条摘要(比如 email 和次数),但业务常要导出或删掉全部重复记录。这时得回原表捞数据:
兼容性最好的写法(MySQL 5.7 / PostgreSQL / SQL Server 都支持):
SELECT * FROM users WHERE email IN ( SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1 );
- 注意:若
email允许为NULL,IN会跳过所有NULL行,需额外补上OR email IS NULL - 性能隐患:大表上子查询可能触发全表扫描,确保
email列有索引 - 更高效替代方案(MySQL 8.0+ / PostgreSQL):用窗口函数
COUNT(*) OVER (PARTITION BY email),一次扫描完成
容易被忽略的 NULL 和大小写陷阱
多个 NULL 在 GROUP BY 中会被归为同一组,COUNT(*) 也会把它们算进去——这符合 SQL 标准,但常被误认为异常。
- 如果业务上不希望
NULL参与重复判定,加前置过滤:WHERE email IS NOT NULL - 如果想单独统计
NULL出现次数,可用:GROUP BY email IS NULL, email(MySQL 支持)或COALESCE(email, '<null>')</null> - 大小写/首尾空格干扰:默认 collation 下,
'Alice '和'alice'可能被当成不同值;必要时用TRIM(LOWER(email))统一处理,并在GROUP BY和SELECT中保持一致
实际跑起来最卡的地方,往往不是语法,而是 NULL 处理是否和业务定义对齐、索引是否建在 GROUP BY 字段上、以及大表时没意识到 IN 子查询可能全表扫两次。










