WHERE在分组前过滤行,HAVING在分组后过滤组;WHERE不能用聚合函数和别名,HAVING可以;HAVING通常需配合GROUP BY使用,且不走索引,过滤粒度为组而非行。

WHERE 和 HAVING 的根本区别在哪
WHERE 是在分组前过滤行,HAVING 是在分组后过滤组。如果你写了 WHERE COUNT(*) > 1,mysql 会直接报错,因为 count(*) 是聚合函数,WHERE 阶段还没开始分组,根本不可用。
常见错误现象:Unknown column 'COUNT(*)' in 'where clause' 或查询结果为空但逻辑上应该有数据。
- WHERE 后只能跟原始字段(如
status、created_at),不能用SUM()、AVG()、COUNT()等 - HAVING 后可以跟聚合函数,也可以跟 select 中的别名(如
HAVING total > 100,前提是 SELECT 里写了SUM(price) AS total) - 执行顺序是:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
HAVING 必须配合 GROUP BY 使用吗
绝大多数情况下是的。单独写 HAVING price > 100 不报错,但行为等价于 WHERE,且失去语义——MySQL 会隐式忽略 HAVING 并当作普通过滤条件处理,容易引发误解。
真正需要 HAVING 的场景,一定是“先分组,再判断组是否符合条件”。比如查出每个部门平均薪资超 15000 的部门,就必须写:
SELECT dept, AVG(salary) AS avg_sal FROM employees GROUP BY dept HAVING avg_sal > 15000;
如果漏掉 GROUP BY dept,AVG(salary) 会算全表均值,HAVING 就只剩一个组可筛,完全失去分组筛选意义。
HAVING 能用索引加速吗
不能。HAVING 是对临时分组结果集做的过滤,发生在内存或临时表中,不走任何索引。性能瓶颈往往出现在 GROUP BY 阶段本身。
优化关键点不在 HAVING,而在前面:
- 确保
GROUP BY字段上有索引(如INDEX(dept)) - WHERE 先尽可能缩小数据量(例如加
WHERE status = 'active'),减少参与分组的行数 - 避免在 GROUP BY 中用函数或表达式(如
GROUP BY YEAR(created_at)),会导致索引失效 - 如果 HAVING 条件非常严格(比如只留 0.1% 的组),考虑能否把部分逻辑前置到 WHERE(但注意:涉及聚合的条件一定不能前置)
MySQL 8.0+ 中 HAVING 和窗口函数能混用吗
不能直接在 HAVING 里引用窗口函数。因为窗口函数是在 SELECT 阶段计算的,而 HAVING 在 GROUP BY 之后、SELECT 之前执行,时间线上不兼容。
想实现“分组后按某窗口指标再过滤”,得用子查询或 CTE:
WITH grouped AS ( SELECT dept, COUNT(*) AS cnt, AVG(salary) OVER (PARTITION BY dept) AS dept_avg FROM employees GROUP BY dept ) SELECT dept, cnt FROM grouped WHERE dept_avg > 15000;
注意:这里外层用的是 WHERE,不是 HAVING——因为 dept_avg 是 CTE 输出的普通列,不再属于聚合结果上下文。
容易被忽略的一点:HAVING 的过滤粒度永远是“组”,不是“行”。哪怕你写 HAVING COUNT(*) = 1,它筛掉的是整个组,组内所有原始行都会消失,没法保留其中某几行。真要保行不保组,得换思路,比如用 ROW_NUMBER() + 子查询。