HAVING是唯一能直接筛选分组结果的子句,作用于GROUP BY之后、ORDER BY之前,支持聚合函数和SELECT别名,但不可引用未分组的原始字段。

HAVING 是唯一能直接筛分组结果的子句
WHERE 作用于原始行,HAVING 才是专为分组后聚合值设计的筛选工具。比如你想找「订单数超过 5 的用户」,COUNT(*) 在分组前根本不存在,WHERE COUNT(*) > 5 必然报错。必须写成:
SELECT user_id, COUNT(*) AS cnt FROM orders GROUP BY user_id HAVING cnt > 5;
-
HAVING中可直接使用聚合函数(COUNT(*)、AVG(salary))和SELECT里的别名(如cnt),但不能用原始表字段(如created_at),除非它也在GROUP BY列表里 - MySQL 5.7+ 默认开启
ONLY_FULL_GROUP_BY,如果HAVING引用了没出现在GROUP BY或聚合中的字段,会直接拒绝执行 -
HAVING发生在GROUP BY之后、ORDER BY之前,它输出的结果才是排序的输入源
ORDER BY 只能排聚合后的结果,不是原始明细
很多人误以为 GROUP BY dept ORDER BY salary DESC 能列出“每个部门工资最高的员工”,其实它只对每个部门的聚合行排序(比如按 AVG(salary) 排),而每个部门只剩一行。真要取每组 Top N,得靠窗口函数:
SELECT dept, name, salary
FROM (
SELECT dept, name, salary,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn
FROM employees
) t
WHERE rn <= 3;
- 直接在
GROUP BY后写ORDER BY salary会报错(除非salary在GROUP BY中或被聚合),因为分组后已无单条salary值 -
ROW_NUMBER()、RANK()、DENSE_RANK()都需搭配PARTITION BY和ORDER BY,缺一不可 - MySQL 5.7 或更早不支持窗口函数,用变量模拟风险高(依赖
ORDER BY执行顺序),升级到 8.0+ 是最稳方案
需要二次加工?子查询或 CTE 是必经之路
HAVING 只能过滤,不能做计算、关联、排名等后续操作。比如「找出订单数高于平均订单数的用户」,必须把分组结果先拎出来再算均值:
WITH order_counts AS ( SELECT user_id, COUNT(*) AS cnt FROM orders GROUP BY user_id ) SELECT user_id, cnt FROM order_counts WHERE cnt > (SELECT AVG(cnt) FROM order_counts);
- MySQL/PostgreSQL 要求子查询必须有别名,漏掉
AS t会报Every derived table must have its own alias - CTE 比嵌套子查询更清晰,尤其当同一分组结果要被多次引用(比如既取 Top 5,又算标准差)
- 别在子查询里用
ORDER BY(除非配合LIMIT),外层才控制最终顺序;子查询内排序对结果无影响 - 性能敏感时注意:大表分组后再
JOIN,有时比先JOIN再分组更慢——优化器可能无法预估中间结果集大小,导致索引失效
真正容易被忽略的是执行顺序的刚性:WHERE → GROUP BY → HAVING → ORDER BY。任何想绕过这个链条的操作(比如在 WHERE 里用聚合、在 HAVING 里引用外层字段),都会触发语法错误或逻辑偏差。窗口函数和 CTE 不是语法糖,而是填补这个链条断裂处的必要机制。

















