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

    • 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 关键字与保留字大全

GROUP BY

导学

聚合函数单独使用时只能产生一行汇总结果。配合 GROUP BY,你就能实现"按维度分组,分别统计"——这是数据分析和报表 SQL 中最核心的模式。

定义

GROUP BY:将具有相同值的行分到同一组,然后对每组应用聚合函数,最终每组产生一行汇总结果。

核心语法

SELECT 分组列, 聚合函数(列)
FROM 表
[WHERE 条件]
GROUP BY 分组列
[WITH ROLLUP]
[HAVING 组过滤条件];

完整示例一:按部门统计人数

第一步:建表并插入数据

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

第二步:当前数据状态

SELECT * FROM employees;
emp_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

第三步:执行 GROUP BY 查询

SELECT
    dept AS 部门,
    COUNT(*) AS 人数,
    COUNT(score) AS 有分数人数,
    AVG(score) AS 平均分,
    MAX(score) AS 最高分,
    MIN(score) AS 最低分
FROM employees
GROUP BY dept;

第四步:操作后的结果

部门人数有分数人数平均分最高分最低分
技术部21100.0000100100

结果解读

  • COUNT(*) 统计每个部门的记录数,共 2 人
  • COUNT(score) 统计 score 非 NULL 的人数,只有大翔有分数,共 1 人
  • AVG(score) 计算每个部门的平均成绩,NULL 不参与计算,结果为 100
  • MAX(score) 和 MIN(score) 分别为 100 和 100
  • 原表有 2 行,分组后每组产生一行汇总,共 1 行

完整示例二:多列分组

当前数据状态

基于上面的 employees 表:

emp_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

执行多列分组查询

SELECT
    dept AS 部门,
    emp_name AS 姓名,
    COUNT(*) AS 人数,
    AVG(score) AS 平均分
FROM employees
GROUP BY dept, emp_name
ORDER BY dept, emp_name;

操作后的结果

部门姓名人数平均分
技术部白歌1NULL
技术部大翔1100.0000

结果解读

  • 先按 dept 分组,同一部门内再按 emp_name 分组
  • 分组列的顺序影响结果展示顺序,不影响聚合值(因为分组是组合的)
  • 原表 2 行被分为 2 个小组,每组一行
  • 白歌的 score 为 NULL,因此其平均分显示为 NULL

完整示例三:WITH ROLLUP(汇总行)

当前数据状态

基于上面的 employees 表。

执行 WITH ROLLUP 查询

SELECT
    dept AS 部门,
    emp_name AS 姓名,
    COUNT(*) AS 人数,
    ROUND(AVG(score), 2) AS 平均分
FROM employees
GROUP BY dept, emp_name WITH ROLLUP;

操作后的结果

部门姓名人数平均分
技术部白歌1NULL
技术部大翔1100.00
技术部NULL2100.00
NULLNULL2100.00

结果解读

  • WITH ROLLUP 在分组汇总的基础上,自动生成各层级的小计和总计
  • 技术部 | NULL 表示技术部的小计(不分姓名)
  • NULL | NULL 表示全部数据的总计
  • 汇总行的分组列显示为 NULL
  • 注意:AVG(score) 中 NULL 不参与计算,因此技术部小计和总计的平均分都是 100.00

完整示例四:SELECT 列的规则与 ONLY_FULL_GROUP_BY

当前数据状态

基于上面的 employees 表。

错误写法演示

-- 错误:SELECT 中出现了非聚合列 emp_name,且不在 GROUP BY 中
SELECT dept, emp_name, COUNT(*) FROM employees GROUP BY dept;

在 MySQL 5.7 默认启用 ONLY_FULL_GROUP_BY 模式下,会报错:

ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause
and contains nonaggregated column 'test.employees.emp_name' which is not
functionally dependent on columns in GROUP BY clause

正确写法一:将非聚合列加入 GROUP BY

SELECT dept, emp_name, COUNT(*)
FROM employees
GROUP BY dept, emp_name;
deptemp_nameCOUNT(*)
技术部白歌1
技术部大翔1

正确写法二:使用 ANY_VALUE() 函数

SELECT dept, ANY_VALUE(emp_name) AS 任意姓名, COUNT(*) AS 人数
FROM employees
GROUP BY dept;
dept任意姓名人数
技术部大翔2

注意:ANY_VALUE(emp_name) 返回该组内任意一个 emp_name 值,结果不确定,仅用于满足语法要求。

结果解读

  • MySQL 5.7 默认 SQL Mode 包含 ONLY_FULL_GROUP_BY
  • 要求 SELECT 列表中的非聚合列必须出现在 GROUP BY 子句中
  • 或者使用 ANY_VALUE() 函数包裹
  • 如果一个组内有多个 emp_name 值,数据库不知道应该返回哪一个,因此需要明确指定

完整示例五:GROUP BY 与 ORDER BY 配合

当前数据状态

基于上面的 employees 表。

执行查询

SELECT
    dept AS 部门,
    COUNT(*) AS 人数,
    ROUND(AVG(score), 2) AS 平均分
FROM employees
GROUP BY dept
ORDER BY 平均分 DESC;

操作后的结果

部门人数平均分
技术部2100.00

结果解读

  • MySQL 5.7 中 GROUP BY 不再隐式排序,如需排序必须显式写 ORDER BY
  • 本例只有技术部一个分组,按平均分降序排列

完整示例六:GROUP BY 与 WHERE 配合

当前数据状态

基于上面的 employees 表。

执行查询

SELECT
    dept AS 部门,
    COUNT(*) AS 人数,
    ROUND(AVG(score), 2) AS 平均分
FROM employees
WHERE score IS NOT NULL
GROUP BY dept;

操作后的结果

部门人数平均分
技术部1100.00

结果解读

  • WHERE score IS NOT NULL 在分组前先过滤掉 score 为 NULL 的记录
  • 白歌(技术部,NULL)被过滤,因此技术部只剩 1 人
  • WHERE 在 GROUP BY 之前执行,不能对聚合结果进行过滤

常见误区

误区正解
GROUP BY 后可以用 WHERE 过滤聚合结果WHERE 在分组前过滤行,聚合结果的过滤用 HAVING。
SELECT * 可以和 GROUP BY 一起用不建议。ONLY_FULL_GROUP_BY 模式下会报错。
GROUP BY 默认按聚合结果排序MySQL 5.7 中 GROUP BY 不再隐式排序,需显式写 ORDER BY。

面试考点

Q:WHERE 和 HAVING 的区别?

WHERE 在 GROUP BY 之前过滤原始行,不能使用聚合函数;HAVING 在 GROUP BY 之后过滤分组结果,可以使用聚合函数。

Q:MySQL 5.7 的 ONLY_FULL_GROUP_BY 是什么?

一个 SQL Mode 设置,要求 SELECT 中的非聚合列必须出现在 GROUP BY 中。这符合 SQL 标准,防止返回不确定的分组值。

Q:GROUP BY 后需要加 ORDER BY 吗?

在 MySQL 5.7 之前,GROUP BY 会隐式按分组列排序;5.7 及之后不再保证这一点。如需排序,必须显式写 ORDER BY。

小结

  • GROUP BY 将数据按维度分组,配合聚合函数产生汇总
  • WITH ROLLUP 自动生成小计和总计
  • MySQL 5.7 默认启用 ONLY_FULL_GROUP_BY,SELECT 中的非聚合列必须在 GROUP BY 中
  • 聚合结果的过滤用 HAVING,不能用 WHERE

下一章引子:GROUP BY 产生了分组,HAVING 则负责过滤这些分组——它是聚合版本的 WHERE。

上一页
UNION
下一页
HAVING