where不能直接过滤分组后的数量,因为where在分组前执行,此时count(*)尚未计算,数据库报错“invalid use of aggregate function”;必须用having,它作用于group by之后,专用于过滤分组结果。

为什么WHERE不能直接过滤分组后的数量
因为 WHERE 在分组前执行,此时 COUNT(*) 还没算出来,数据库会直接报错:「Invalid use of aggregate function」。必须用 HAVING —— 它专为过滤分组结果而设,作用在 GROUP BY 之后。
基本写法:先GROUP BY,再HAVING COUNT(*) > 1
假设有一张 users 表,想找出邮箱重复的记录:
SELECT email, COUNT(*) AS cnt FROM users GROUP BY email HAVING COUNT(*) > 1;
注意点:
-
SELECT中所有非聚合字段(如email)必须出现在GROUP BY中 -
HAVING后可直接写COUNT(*) > 1,也可用别名cnt > 1(但不是所有数据库都支持,PostgreSQL 支持,MySQL 5.7 默认不支持) - 如果还想看具体是哪些行,得用子查询或窗口函数,
HAVING本身只返回分组摘要
想查出全部重复行(不止分组摘要)怎么办
这时 HAVING 不够用了,得结合主键或自连接。常见做法是用子查询先找出重复值,再关联原表:
SELECT * FROM users WHERE email IN ( SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1 );
或者用窗口函数(MySQL 8.0+/PostgreSQL/SQL Server)更高效:
SELECT id, email FROM ( SELECT id, email, COUNT(*) OVER (PARTITION BY email) AS cnt FROM users ) t WHERE cnt > 1;
关键区别:
- 子查询方式可能对大表产生全表扫描+两次遍历,性能弱
- 窗口函数只需一次扫描,且能保留原始行的所有字段
- 如果表没主键或唯一标识,用
DISTINCT+GROUP BY可能漏掉细节,得小心业务语义
容易被忽略的NULL陷阱
GROUP BY 会把所有 NULL 归为同一组,所以 COUNT(*) 会把空邮箱也当重复项统计。如果你不希望 NULL 参与去重判断,得显式排除:
SELECT email, COUNT(*) FROM users WHERE email IS NOT NULL GROUP BY email HAVING COUNT(*) > 1;
否则可能出现「查出 5 条重复邮箱,结果全是 NULL」这种意外结果。另外,不同数据库对 NULL = NULL 的处理一致,但前端展示时容易误判为空数据而非有效重复值。
真正难的不是写对 HAVING,而是想清楚「重复」在你业务里到底指什么——是字段完全相等?忽略大小写?要排除空值?这些都会直接影响 GROUP BY 前的清洗逻辑。










