乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 数据库基础与安装

    • MySQL 简介
    • MySQL 5.6 到 5.7 到 8.0 关键差异速查
    • 安装 MySQL 5.7
    • 连接与断开服务器
    • 创建数据库
    • 创建数据表
    • 数据库与数据表
    • 加载数据
    • 获取数据库信息
    • 批处理模式
    • SHOW 语句汇总
    • FLUSH 与 RESET 语句
    • my.cnf 核心参数
    • 字符集与排序规则
  • 第2章 SQL基础查询

    • SELECT
    • WHERE
    • ORDER BY
    • LIMIT
    • COUNT
    • 聚合函数
    • 比较运算符
    • 逻辑运算符
    • 算术运算符
    • 模式匹配
    • NULL 值处理
    • UPDATE
    • DELETE
    • REPLACE
    • SELECT INTO
  • 第3章 数据类型与运算符

    • 数值类型
    • 字符串类型
    • 日期时间类型
    • BIT 类型
    • ENUM 类型
    • SET 类型
    • JSON 类型
    • 类型转换
  • 第4章 函数与表达式

    • 字符串函数
    • 数值函数
    • 日期函数
    • 全文检索函数
  • 第5章 高级查询与子查询

    • JOIN
    • 子查询
    • UNION
    • GROUP BY
    • HAVING
    • DISTINCT
  • 第6章 表与索引

    • 数据定义语言
    • 修改表结构
    • 视图
    • 修改视图与检查选项
    • 外键
    • 索引
    • 唯一索引
    • 复合索引
    • 存储引擎对比
    • 分区表
    • 第一范式与第二范式
    • 第三范式与 BC 范式
    • 反范式设计
  • 第7章 存储过程与函数

    • 存储过程
    • 存储函数
    • 变量
    • 流程控制
    • 游标
    • 预处理语句
  • 第8章 事务与锁

    • 事务
    • 事务隔离级别
    • 锁机制
    • MVCC
    • 死锁专题分析
    • LOCK TABLES
    • XA 事务
  • 第9章 用户管理与安全

    • 用户管理
    • 权限管理
    • 角色
    • SQL 注入防范
  • 第10章 性能优化入门

    • 执行计划
    • 索引优化
    • 查询优化
    • 查询优化器提示
    • 慢查询日志
    • InnoDB 深入机制
    • InnoDB 专项优化
    • Performance Schema
    • sys Schema
  • 第11章 复制与高可用

    • 主从复制原理
    • 半同步复制配置
    • binlog 开启与 point-in-time 恢复
    • mysqldump 全库备份
    • mysqldump 单表与条件备份
    • mysqldump 恢复与导入
    • xtrabackup 全量热备
    • xtrabackup 准备与恢复
    • xtrabackup 增量与流式备份
  • 第12章 触发器与事件

    • 触发器
    • 事件调度器
  • 参考

    • MySQL 5.7 专业术语大全
    • MySQL 5.7 关键字与保留字大全

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_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

执行 HAVING 过滤

SELECT dept, COUNT(*) AS cnt
FROM employees
GROUP BY dept
HAVING COUNT(*) > 1;

操作后的结果

deptcnt
技术部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人数平均分最高分
技术部2100.00100

结果解读

  • 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;

操作后的结果

deptcnt平均分
技术部1100.00

执行顺序分析

  1. WHERE score IS NOT NULL:先过滤出 score 不为 NULL 的员工(共 1 人:大翔)
  2. GROUP BY dept:按部门分组
  3. 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;

操作后的结果

deptcntavg_score
技术部2100.00

结果解读

  • 标准 SQL 不允许在 HAVING 中使用 SELECT 别名,但 MySQL 允许
  • HAVING cnt >= 2 等价于 HAVING COUNT(*) >= 2
  • HAVING avg_score > 50 等价于 HAVING AVG(score) > 50
  • 这是 MySQL 的扩展语法,移植到其他数据库时需注意兼容性

完整示例五:HAVING 过滤非聚合列(不推荐但可行)

当前数据状态

基于上面的 employees 表。

执行查询

SELECT dept, COUNT(*) AS cnt
FROM employees
GROUP BY dept
HAVING dept LIKE '%技术%';

操作后的结果

deptcnt
技术部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 对比

特性WHEREHAVING
执行时机分组前分组后
过滤对象原始行分组结果
可用聚合函数不可以可以
可用 SELECT 别名不可以MySQL 允许
性能先过滤减少数据量,更优对聚合结果过滤

常见误区

误区正解
HAVING 可以替代 WHEREHAVING 在聚合后执行,无法利用索引过滤原始行。能用 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 的去重有什么区别?

上一页
GROUP BY
下一页
DISTINCT