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_id | emp_name | dept | score |
|---|---|---|---|
| 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;
第四步:操作后的结果
| 部门 | 人数 | 有分数人数 | 平均分 | 最高分 | 最低分 |
|---|---|---|---|---|---|
| 技术部 | 2 | 1 | 100.0000 | 100 | 100 |
结果解读
COUNT(*)统计每个部门的记录数,共 2 人COUNT(score)统计 score 非 NULL 的人数,只有大翔有分数,共 1 人AVG(score)计算每个部门的平均成绩,NULL 不参与计算,结果为 100MAX(score)和MIN(score)分别为 100 和 100- 原表有 2 行,分组后每组产生一行汇总,共 1 行
完整示例二:多列分组
当前数据状态
基于上面的 employees 表:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 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;
操作后的结果
| 部门 | 姓名 | 人数 | 平均分 |
|---|---|---|---|
| 技术部 | 白歌 | 1 | NULL |
| 技术部 | 大翔 | 1 | 100.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;
操作后的结果
| 部门 | 姓名 | 人数 | 平均分 |
|---|---|---|---|
| 技术部 | 白歌 | 1 | NULL |
| 技术部 | 大翔 | 1 | 100.00 |
| 技术部 | NULL | 2 | 100.00 |
| NULL | NULL | 2 | 100.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;
| dept | emp_name | COUNT(*) |
|---|---|---|
| 技术部 | 白歌 | 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;
操作后的结果
| 部门 | 人数 | 平均分 |
|---|---|---|
| 技术部 | 2 | 100.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;
操作后的结果
| 部门 | 人数 | 平均分 |
|---|---|---|
| 技术部 | 1 | 100.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。