HAVING必须紧跟GROUP BY之后,不能替代WHERE;它仅用于筛选分组后的聚合结果,而WHERE负责分组前的行级过滤,二者执行时机、作用对象和允许使用的表达式均不同。
很多人写 HAVING 时直接接在 SELECT 后,结果报错:ERROR: HAVING clause without GROUP BY。这是因为 HAVING 只能作用于分组后的聚合结果,不是行级过滤——那是 WHERE 的事。
典型错误写法:SELECT user_id, COUNT(*) FROM logs HAVING COUNT(*) > 5;(缺少 GROUP BY)
正确结构必须是:SELECT ... FROM ... GROUP BY ... HAVING ...
WHERE 过滤原始行(在分组前),支持字段名、普通条件,如 WHERE status = 'active'
HAVING 过滤分组后结果(在 GROUP BY 之后),可引用聚合函数,如 HAVING COUNT(*) >= 3
WHERE created_at > '2024-01-01' GROUP BY user_id HAVING SUM(amount) > 100
写 SELECT AVG(score) AS avg_score FROM exams GROUP BY class HAVING avg_score > 85; 会失败(MySQL 5.7+ 默认严格模式下报错:Unknown column 'avg_score' in 'having clause')。HAVING 解析早于别名绑定,不认 AS 定义的别名。
必须写成:HAVING AVG(score) > 85,或者用完整表达式(比如 HAVING (SUM(score)/COUNT(*)) > 85)。
HAVING 中用别名,但 MySQL、SQL Server、SQLite 不支持——跨数据库迁移时尤其容易踩坑SELECT 和 ORDER BY 中安全可用;WHERE、HAVING、GROUP BY 都不行聚合函数遇到全 NULL 分组时,COUNT(*) 返回 0,但 COUNT(col)、SUM(col)、AVG(col) 返回 NULL。这时候 HAVING SUM(revenue) > 1000 会把该分组整个跳过——因为 NULL > 1000 结果是 UNKNOWN,不满足真值条件。
这不是 bug,是 SQL 三值逻辑的正常表现,但常被忽略。
HAVING SUM(revenue) > 1000 OR SUM(revenue) IS NULL
HAVING SUM(revenue) IS NOT NULL AND SUM(revenue) > 1000
COUNT(*) 永远非 NULL,适合做存在性判断,比如 HAVING COUNT(*) >= 1 总是成立,但没意义;真正有用的是 HAVING COUNT(col) > 0 确认某列有非空值HAVING 是在内存中对已分组结果做筛选,不像 WHERE 可以借助索引快速排除行。如果 GROUP BY 产生几万组,再用 HAVING 扫一遍,效率明显下降。
WHERE:比如想查“近30天下单超5次的用户”,写成 WHERE order_time >= NOW() - INTERVAL 30 DAY,而不是让 HAVING COUNT(*) > 5 处理全部历史数据HAVING 里调用函数,如 HAVING YEAR(created_at) = 2024,这会让引擎无法使用 created_at 索引GROUP BY + HAVING 很可能成为瓶颈,考虑是否真需要聚合,或改用应用层二次过滤实际用的时候,先问自己一句:这个条件是筛原始行,还是筛分组桶?筛桶才轮到 HAVING 出场;一上来就写 HAVING,八成是逻辑没理清。