HAVING是唯一正确筛选分组后聚合结果的方式,必须紧跟GROUP BY;WHERE只能过滤分组前行,不可用COUNT(*)等聚合函数,否则语法报错。

用 GROUP BY + HAVING 筛出超阈值的分组
直接在分组后加条件过滤,HAVING 是唯一正确方式。它作用于分组结果,而 WHERE 只能过滤原始行,放错位置会报错或逻辑错误。
常见错误是把数量条件写在 WHERE 里,比如 WHERE COUNT(*) > 5 —— 这语法非法,因为 COUNT(*) 在 WHERE 阶段还没计算出来。
-
HAVING必须跟在GROUP BY后面,顺序不能颠倒 - 若同时有
WHERE和HAVING,先执行WHERE(减少输入分组的数据量),再分组,最后HAVING过滤分组结果 - 所有出现在
HAVING中的非聚合字段,必须也在GROUP BY列表中(严格模式下)
查重复主键或业务唯一键的异常记录
这是最典型的“找异常分组”场景:某张表本该主键唯一,但发现重复;或某个业务字段(如 order_no、phone)不该重复却重复了。
示例:查出重复的 user_id 及其出现次数
SELECT user_id, COUNT(*) AS cnt FROM orders GROUP BY user_id HAVING COUNT(*) > 1;
- 如果要定位具体哪几条记录,得用子查询或窗口函数配合
IN,单纯GROUP BY + HAVING只返回分组摘要 - 注意
COUNT(*)和COUNT(column)区别:后者忽略NULL,前者统计所有行(包括全为NULL的行) - 在 MySQL 8.0+ 或 PostgreSQL 中,可用
COUNT(*) OVER (PARTITION BY user_id)先打标再过滤,避免二次扫描
性能敏感时避免全表扫描和临时表
当数据量大、阈值小(比如 > 1)、且没有索引时,GROUP BY 很容易触发磁盘临时表和 filesort,查询变慢甚至 OOM。
- 确保
GROUP BY字段上有索引,尤其是复合索引的最左前缀匹配(如按status, user_id分组,索引应为(status, user_id)) - 若只关心“是否存在超阈值分组”,可用
LIMIT 1配合HAVING提前终止(MySQL 支持,PostgreSQL 需用EXISTS子查询) - 对超大表,考虑先用采样(
TABLESAMPLE)或分区裁剪缩小范围,再精确扫描
跨多列组合判断异常(如联合唯一约束失效)
业务上常要求多个字段组合唯一(如 (tenant_id, config_key)),一旦违反就是配置冲突。这时分组字段不止一个。
示例:查出同一租户下重复的配置项
SELECT tenant_id, config_key, COUNT(*) AS dup_cnt FROM app_configs GROUP BY tenant_id, config_key HAVING COUNT(*) > 1;
- 多列
GROUP BY时,HAVING中的聚合表达式不能只引用其中一部分列(除非是聚合函数结果) - 若想看每组的具体重复行 ID,可改用窗口函数:
SELECT *, COUNT(*) OVER (PARTITION BY tenant_id, config_key) AS cnt FROM app_configs,再外层WHERE cnt > 1 - 注意 NULL 值参与分组:
(1, NULL)和(1, NULL)在大多数 SQL 引擎中会被视为相同分组,但某些旧版本 MySQL 可能不一致
HAVING 和 WHERE 的执行阶段差异,以及多列分组时 NULL 的隐式处理行为。这两个点一旦出错,结果就不可信。

















