写SQL的时候,HA VING 这个子句看着简单,但实际用起来踩坑的人可不少。先说个核心判断:它是专门用来过滤分组后的聚合结果的,跟 WHERE 完全是两码事。很多人搞混它俩,结果代码跑出来要么报错,要么数据对不上。下面把这几个核心要点拆开来说清楚。

HA VING 必须跟在 GROUP BY 后面,不能替代 WHERE
不少人一上来就写 HA VING,直接跟在 SELECT 后面,结果数据库甩回来一个 ERROR: HA VING clause without GROUP BY。根本原因在于,HA VING 只对已经分好组的数据起作用,它不是用来过滤单条记录的——那活儿归 WHERE 管。
一个典型的错误写法是这样的:
SELECT user_id, COUNT(*) FROM logs HA VING COUNT(*) > 5;
—— 缺少 GROUP BY,直接报错。
正确的结构必须长这样:SELECT ... FROM ... GROUP BY ... HA VING ...
WHERE在分组之前过滤原始行,只能写字段名或普通条件,比如WHERE status = 'active'HA VING在GROUP BY之后过滤分组结果,里面可以写聚合函数,比如HA VING COUNT(*) >= 3- 如果既要筛行又要筛组,两个子句完全可以共存:
WHERE created_at > '2024-01-01' GROUP BY user_id HA VING SUM(amount) > 100
简单说,WHERE 是行级过滤,HA VING 是组级过滤,执行时机、作用对象都不一样,不能混着用。
HA VING 中不能用 SELECT 别名,得重复表达式
还有一个很常见的坑:想偷懒用别名。
比如写 SELECT A VG(score) AS a vg_score FROM exams GROUP BY class HA VING a vg_score > 85; —— 在 MySQL 5.7 及以上版本的严格模式下,这行会直接报错:Unknown column 'a vg_score' in 'ha ving clause'。
原因是 HA VING 的解析顺序比别名绑定早,它不认识什么 AS 定义的别名。必须老老实实把表达式再写一遍:HA VING A VG(score) > 85,或者更完整一点 HA VING (SUM(score)/COUNT(*)) > 85。
- 得注意:PostgreSQL 允许在
HA VING里用别名,但 MySQL、SQL Server、SQLite 都不支持——跨数据库迁移时尤其容易在这里翻车 - 别名最安全的“活动范围”其实只有
SELECT和ORDER BY;WHERE、HA VING、GROUP BY这些地方都别指望它 - 复杂表达式要重复写两遍,容易手滑出错。建议先把逻辑想清楚,再复制粘贴,别靠脑子临时改
NULL 值在 HA VING 中的处理很隐蔽
这一点经常被忽略,但坑起来真能让人查半天。当聚合函数遇到全为 NULL 的分组时,COUNT(*) 返回 0,但 COUNT(col)、SUM(col)、A VG(col) 统统返回 NULL。这时候如果写 HA VING SUM(revenue) > 1000,这个分组就会整个被跳过——因为 NULL > 1000 的结果是 UNKNOWN,不满足真值条件。
这不是 bug,是 SQL 三值逻辑的正常表现,但知道的人确实不多。
- 想包含 NULL 聚合结果的分组,得显式判断:
HA VING SUM(revenue) > 1000 OR SUM(revenue) IS NULL - 更常见的需求其实是“排除 NULL”,那就用
HA VING SUM(revenue) IS NOT NULL AND SUM(revenue) > 1000 COUNT(*)永远不为 NULL,适合做存在性判断。但HA VING COUNT(*) >= 1总是成立,没实际意义;真正有用的是HA VING COUNT(col) > 0,用来确认某列有非空值
性能上,HA VING 无法利用索引,慎用于大数据量分组
这一点必须警惕。HA VING 是在内存里对已经分组完成的结果做筛选,不像 WHERE 可以借助索引快速排除行。如果 GROUP BY 生成了几万个分组,再用 HA VING 全扫一遍,效率会明显下降。
- 优先把能下推的条件放进
WHERE:比如想查“近30天下单超5次的用户”,应该先写WHERE order_time >= NOW() - INTERVAL 30 DAY,减少参与分组的行数,而不是让HA VING COUNT(*) > 5去处理全部历史数据 - 避免在
HA VING里调用函数,比如HA VING YEAR(created_at) = 2024,这会让引擎无法使用created_at列的索引 - 如果分组键基数极高,比如按 UUID 分组,
GROUP BY + HA VING很容易变成性能瓶颈。这时候需要想想是否真的要聚合,或者考虑在应用层做二次过滤
实际写代码的时候,先问自己一句:这个条件是要过滤原始行,还是过滤分组桶?只有筛桶的时候才轮到 HA VING 出场;一上来就写 HA VING,八成是逻辑还没理清楚。
