HAVING
导学
WHERE 过滤的是原始行,但如果想过滤"平均分大于 80 的班级"这类基于聚合结果的条件,WHERE 就无能为力了。HAVING 正是为这种需求而生。
定义
HAVING:用于对 GROUP BY 分组后的结果进行过滤。与 WHERE 不同,HAVING 可以使用聚合函数,因为它在聚合计算完成后执行。
核心语法
SELECT 列, 聚合函数(列)
FROM 表
WHERE 行过滤条件
GROUP BY 分组列
HAVING 组过滤条件
ORDER BY 排序列;
完整示例准备:建表与数据
CREATE TABLE employees (
emp_id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(20),
dept VARCHAR(20),
score DECIMAL(5,2)
);
INSERT INTO employees (emp_name, dept, score) VALUES
('大翔', '技术部', 100),
('白歌', '技术部', NULL);
完整示例一:过滤聚合结果——人数超过 1 人的部门
当前数据状态
SELECT * FROM employees;
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
执行 HAVING 过滤
SELECT dept, COUNT(*) AS cnt
FROM employees
GROUP BY dept
HAVING COUNT(*) > 1;
操作后的结果
| dept | cnt |
|---|---|
| 技术部 | 2 |
结果解读
COUNT(*) > 1是聚合后的条件,必须用HAVING- 如果放在
WHERE中会报错,因为WHERE执行时还没有完成分组和聚合 - 技术部有 2 人,满足条件,因此返回
完整示例二:HAVING 使用多个聚合条件
当前数据状态
基于上面的 employees 表。
执行多条件 HAVING
SELECT
dept,
COUNT(*) AS 人数,
ROUND(AVG(score), 2) AS 平均分,
MAX(score) AS 最高分
FROM employees
GROUP BY dept
HAVING COUNT(*) >= 2
AND AVG(score) > 50;
操作后的结果
| dept | 人数 | 平均分 | 最高分 |
|---|---|---|---|
| 技术部 | 2 | 100.00 | 100 |
结果解读
HAVING中可以使用多个聚合条件,用AND/OR组合- 技术部平均分
100.00,高于 50,满足条件 COUNT(*) >= 2和AVG(score) > 50同时满足,因此保留
完整示例三:WHERE 和 HAVING 结合使用
当前数据状态
基于上面的 employees 表。
执行 WHERE + HAVING 组合查询
-- 查询 score 不为 NULL 的、人数达到 1 人的部门
SELECT dept, COUNT(*) AS cnt, ROUND(AVG(score), 2) AS 平均分
FROM employees
WHERE score IS NOT NULL
GROUP BY dept
HAVING COUNT(*) >= 1;
操作后的结果
| dept | cnt | 平均分 |
|---|---|---|
| 技术部 | 1 | 100.00 |
执行顺序分析
WHERE score IS NOT NULL:先过滤出 score 不为 NULL 的员工(共 1 人:大翔)GROUP BY dept:按部门分组HAVING COUNT(*) >= 1:过滤人数达到 1 的部门
结果解读
WHERE先过滤原始行,减少进入分组的数据量HAVING再对分组结果进行过滤- 先用
WHERE过滤原始数据,再用HAVING过滤聚合结果,是标准且高效的写法
完整示例四:HAVING 中使用 SELECT 别名(MySQL 扩展)
当前数据状态
基于上面的 employees 表。
执行查询
SELECT dept, COUNT(*) AS cnt, ROUND(AVG(score), 2) AS avg_score
FROM employees
GROUP BY dept
HAVING cnt >= 2 AND avg_score > 50;
操作后的结果
| dept | cnt | avg_score |
|---|---|---|
| 技术部 | 2 | 100.00 |
结果解读
- 标准 SQL 不允许在
HAVING中使用SELECT别名,但 MySQL 允许 HAVING cnt >= 2等价于HAVING COUNT(*) >= 2HAVING avg_score > 50等价于HAVING AVG(score) > 50- 这是 MySQL 的扩展语法,移植到其他数据库时需注意兼容性
完整示例五:HAVING 过滤非聚合列(不推荐但可行)
当前数据状态
基于上面的 employees 表。
执行查询
SELECT dept, COUNT(*) AS cnt
FROM employees
GROUP BY dept
HAVING dept LIKE '%技术%';
操作后的结果
| dept | cnt |
|---|---|
| 技术部 | 2 |
结果解读
HAVING中可以使用非聚合列条件,但不高效- 普通列条件应放在
WHERE中,在分组前过滤,减少数据量 - 上面的查询应改写为:
SELECT dept, COUNT(*) AS cnt
FROM employees
WHERE dept LIKE '%技术%'
GROUP BY dept;
完整示例六:没有 GROUP BY 时使用 HAVING
当前数据状态
基于上面的 employees 表。
执行查询
SELECT COUNT(*) AS total_employees
FROM employees
HAVING COUNT(*) > 1;
操作后的结果
| total_employees |
|---|
| 2 |
修改条件后再执行
SELECT COUNT(*) AS total_employees
FROM employees
HAVING COUNT(*) > 100;
操作后的结果
| total_employees |
|---|
| (空) |
结果解读
- 没有
GROUP BY时,HAVING过滤的是全表聚合后的单一结果行 COUNT(*) = 2,满足> 1,因此返回 1 行COUNT(*) = 2,不满足> 100,因此返回空集- 这种写法虽然不常见,但完全合法
WHERE vs HAVING 对比
| 特性 | WHERE | HAVING |
|---|---|---|
| 执行时机 | 分组前 | 分组后 |
| 过滤对象 | 原始行 | 分组结果 |
| 可用聚合函数 | 不可以 | 可以 |
| 可用 SELECT 别名 | 不可以 | MySQL 允许 |
| 性能 | 先过滤减少数据量,更优 | 对聚合结果过滤 |
常见误区
| 误区 | 正解 |
|---|---|
HAVING 可以替代 WHERE | HAVING 在聚合后执行,无法利用索引过滤原始行。能用 WHERE 的不要用 HAVING。 |
HAVING 中不能用普通列条件 | 可以,但不高效。普通列条件应放在 WHERE 中。 |
HAVING 必须配合 GROUP BY | 不必须。没有 GROUP BY 时,HAVING 过滤的是全表聚合后的单一结果行。 |
面试考点
Q:WHERE 和 HAVING 的执行顺序?
FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY→LIMIT。WHERE先过滤行,HAVING后过滤组。
Q:能用 WHERE 的为什么要避免用 HAVING?
WHERE在分组前过滤,可以减少进入分组和聚合的数据量,更高效。HAVING对所有分组后的结果进行过滤,数据量更大。
Q:没有 GROUP BY 时 HAVING 有用吗?
有用。
SELECT COUNT(*) FROM table HAVING COUNT(*) > 0;这种写法虽然不常见,但合法。没有GROUP BY时,整个结果集视为一个组。
小结
HAVING过滤分组后的聚合结果,WHERE过滤原始行HAVING可以使用聚合函数,WHERE不可以- 能用
WHERE的过滤条件不要放到HAVING中,以提高性能 - MySQL 允许在
HAVING中使用 SELECT 别名
下一章引子:到本章为止,你已经掌握了 JOIN、子查询、UNION、GROUP BY 和 HAVING。回顾一下,DISTINCT 其实也是一个"去重"操作,但它和 GROUP BY 的去重有什么区别?