MySQL 聚合与分组完全指南
本文档专门讲 聚合函数(COUNT、SUM、AVG、MAX、MIN)和 分组(GROUP BY)、分组后过滤(HAVING)。每一步都配有详细说明和大量示例,适合零基础新手跟着做。
目录
- 什么是聚合与分组
- 示例表与数据准备
- 聚合函数 COUNT(计数)
- 聚合函数 SUM(求和)
- 聚合函数 AVG(平均值)
- 聚合函数 MAX 与 MIN(最大、最小)
- 聚合函数与 NULL
- 分组 GROUP BY
- 分组后过滤 HAVING
- WHERE、GROUP BY、HAVING、ORDER BY 的顺序
- 多列分组与常见用法
- 常见错误与注意点
- 综合示例与速查表
1. 什么是聚合与分组
1.1 聚合(Aggregation)
聚合就是把多行数据算成一个结果,例如:
- 总共有多少行?→ COUNT
- 某列加起来是多少?→ SUM
- 某列的平均值?→ AVG
- 某列最大、最小值?→ MAX、MIN
这些“把多行变成一行”的函数叫 聚合函数。
1.2 分组(Grouping)
分组就是先按某一列(或几列)把行分成若干组,再对每一组分别做聚合。
例如:
- 按“班级”分组 → 得到“高一1班一组、高一2班一组……”
- 再对每组做 COUNT → 得到“每个班有多少人”
- 对每组做 AVG(score) → 得到“每个班的平均分”
所以:分组 = 先归类,再在每一类里做聚合。
1.3 在 SQL 里的对应关系
| 概念 | SQL 写法 | 作用 |
|---|---|---|
| 聚合函数 | COUNT、SUM、AVG、MAX、MIN | 对多行算出一个值 |
| 分组 | GROUP BY 列 | 按列(或列组合)分组 |
| 分组后过滤 | HAVING 条件 | 只保留满足条件的“组” |
2. 示例表与数据准备
后面所有示例都基于下面两张表,建议先建好并插入数据。
2.1 学生表 students
CREATE TABLE IF NOT EXISTS students (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
gender CHAR(1) DEFAULT '男',
age INT,
class_name VARCHAR(20)
);
INSERT INTO students (name, gender, age, class_name) VALUES
('张三', '男', 18, '高一1班'),
('李四', '女', 17, '高一2班'),
('王五', '男', 18, '高一1班'),
('赵六', '女', 17, '高一2班'),
('钱七', '男', 19, '高一1班'),
('孙八', '女', 18, '高一2班');
2.2 成绩表 scores
CREATE TABLE IF NOT EXISTS scores (
id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT NOT NULL,
subject VARCHAR(20) NOT NULL,
score DECIMAL(5,2)
);
INSERT INTO scores (student_id, subject, score) VALUES
(1, '语文', 85),
(1, '数学', 92),
(1, '英语', 78),
(2, '语文', 88),
(2, '数学', 76),
(2, '英语', 90),
(3, '语文', 72),
(3, '数学', 85),
(3, '英语', 88),
(4, '语文', 90),
(4, '数学', 82),
(5, '语文', 95),
(5, '数学', 88),
(6, '语文', 80),
(6, '数学', NULL); -- 孙八数学缺考,分数为 NULL
3. 聚合函数 COUNT(计数)
3.1 COUNT(*) —— 统计行数
*COUNT() 统计结果集有多少行**,不关心某列是否为空。
示例:学生总人数
SELECT COUNT(*) AS 学生总数 FROM students;
结果:6(表里 6 个学生)。
示例:成绩记录有多少条
SELECT COUNT(*) AS 成绩条数 FROM scores;
3.2 COUNT(列名) —— 统计该列“非 NULL”的个数
COUNT(列名) 只统计这一列里不是 NULL 的行数。
示例:有成绩的记录数(score 为 NULL 的不算)
SELECT COUNT(score) AS 有效成绩数 FROM scores;
若有一条 score 为 NULL,则结果比 COUNT(*) 少 1。
示例:有年龄的学生人数(age 为 NULL 的不算)
SELECT COUNT(age) AS 有年龄的人数 FROM students;
3.3 COUNT(DISTINCT 列名) —— 去重后计数
COUNT(DISTINCT 列名) 先对该列去重,再数有多少个不同的值。
示例:一共有多少个不同的班级
SELECT COUNT(DISTINCT class_name) AS 班级数 FROM students;
结果:2(高一1班、高一2班)。
示例:有成绩记录的学生人数(不重复的 student_id 个数)
SELECT COUNT(DISTINCT student_id) AS 有成绩的学生数 FROM scores;
3.4 COUNT 小结
| 写法 | 含义 |
|---|---|
| COUNT(*) | 总行数(含 NULL) |
| COUNT(列名) | 该列非 NULL 的行数 |
| COUNT(DISTINCT 列名) | 该列去重后的不同值个数 |
4. 聚合函数 SUM(求和)
4.1 基本用法
SUM(列名) 对该列的数值求和;NULL 会被忽略,不参与计算。
示例:所有成绩的总和
SELECT SUM(score) AS 成绩总和 FROM scores;
示例:某学生的总分(先 WHERE 再 SUM)
SELECT SUM(score) AS 张三总分 FROM scores WHERE student_id = 1;
4.2 没有匹配行时
若 WHERE 后没有一行,或所有行的该列都是 NULL,SUM 结果是 NULL(不是 0)。若希望显示 0,可用 IFNULL(SUM(score), 0)。
SELECT IFNULL(SUM(score), 0) AS 总分 FROM scores WHERE student_id = 999;
4.3 非数值列
对非数值列(如字符串)用 SUM 没有意义,会报错或得到 0,只应对数值型列使用 SUM。
5. 聚合函数 AVG(平均值)
5.1 基本用法
AVG(列名) 对该列求平均值:总和 / 非 NULL 的个数;NULL 不参与计算。
示例:所有成绩的平均分
SELECT AVG(score) AS 平均分 FROM scores;
示例:语文的平均分
SELECT AVG(score) AS 语文平均分 FROM scores WHERE subject = '语文';
5.2 手动算平均(总和/人数)
若想“总和/总人数”(把 NULL 也算成 0 或排除),可以自己写:
SELECT SUM(score) / COUNT(*) AS 平均 FROM scores; -- NULL 行也参与除数
SELECT SUM(score) / COUNT(score) AS 平均 FROM scores; -- 等价于 AVG(score)
一般直接用 AVG(列名) 即可。
6. 聚合函数 MAX 与 MIN(最大、最小)
6.1 MAX(列名) —— 最大值
示例:最高分
SELECT MAX(score) AS 最高分 FROM scores;
示例:每个科目的最高分(这里需要分组,见后文 GROUP BY)
6.2 MIN(列名) —— 最小值
示例:最低分
SELECT MIN(score) AS 最低分 FROM scores;
6.3 对字符串、日期
MAX/MIN 也可用于字符串(按字典序)、日期(按时间先后),例如:
SELECT MAX(name) AS 姓名最大, MIN(created_at) AS 最早时间 FROM 某表;
7. 聚合函数与 NULL
7.1 规则小结
- *COUNT()**:数行数,NULL 行也算一行。
- COUNT(列名):只数该列非 NULL 的行。
- SUM、AVG、MAX、MIN:计算时忽略 NULL;若全是 NULL,结果为 NULL。
7.2 示例对比
假设 scores 里有一条 score 为 NULL:
SELECT COUNT(*) FROM scores; -- 总行数,含 NULL 行
SELECT COUNT(score) FROM scores; -- score 非 NULL 的行数
SELECT AVG(score) FROM scores; -- 只对非 NULL 的 score 求平均
8. 分组 GROUP BY
8.1 作用
GROUP BY 列 表示:先按这一列(或几列)的值相同的行分到一组,再对每一组做聚合,结果每组一行。
8.2 基本语法
SELECT 分组列, 聚合函数(列) ...
FROM 表名
[WHERE 条件]
GROUP BY 分组列
[HAVING 条件] [ORDER BY ...] [LIMIT ...];
- SELECT 里:要么是 GROUP BY 里的列,要么是 聚合函数,否则在某些模式下会报错或结果不确定。
- GROUP BY 后写按哪一列(或哪几列)分组。
8.3 示例:每个班级有多少人
按 class_name 分组,对每组做 *COUNT()**:
SELECT class_name AS 班级, COUNT(*) AS 人数
FROM students
GROUP BY class_name;
示例结果:
+----------+------+
| 班级 | 人数 |
+----------+------+
| 高一1班 | 3 |
| 高一2班 | 3 |
+----------+------+
8.4 示例:每个班级的平均年龄
SELECT class_name AS 班级, AVG(age) AS 平均年龄
FROM students
GROUP BY class_name;
8.5 示例:每个学生的总成绩、平均成绩
按 student_id 分组,对 score 做 SUM 和 AVG:
SELECT student_id AS 学号, SUM(score) AS 总分, AVG(score) AS 平均分
FROM scores
GROUP BY student_id;
若想显示学生姓名,需要和 students 表连接(见后文多表示例),或先用上面结果再根据学号查姓名。
8.6 示例:每个科目的最高分、最低分、平均分
按 subject 分组:
SELECT subject AS 科目,
MAX(score) AS 最高分,
MIN(score) AS 最低分,
AVG(score) AS 平均分
FROM scores
GROUP BY subject;
8.7 SELECT 里能写什么
- 可以写 GROUP BY 里的列(或表达式一致)。
- 可以写 聚合函数,如 COUNT(*)、SUM(score)、AVG(score)。
- 不要写“既不在 GROUP BY 里,也不是聚合函数”的列(在严格模式下会报错),例如只 GROUP BY class_name 却 SELECT name,同一班有多个人,name 取谁不确定。
9. 分组后过滤 HAVING
9.1 WHERE 和 HAVING 的区别
- WHERE:在分组之前过滤行,不满足条件的行不参与分组和聚合。
- HAVING:在分组之后过滤组,只保留满足条件的组。
9.2 基本语法
SELECT 分组列, 聚合函数(...)
FROM 表名
WHERE ... -- 先筛行
GROUP BY 分组列
HAVING 条件; -- 再筛组
HAVING 的条件里通常会出现聚合函数或分组列。
9.3 示例:人数大于 2 的班级
先按班级分组、算人数,再只保留“人数 > 2”的组:
SELECT class_name AS 班级, COUNT(*) AS 人数
FROM students
GROUP BY class_name
HAVING COUNT(*) > 2;
9.4 示例:平均分大于 85 的学生(按学号分组)
SELECT student_id AS 学号, AVG(score) AS 平均分
FROM scores
GROUP BY student_id
HAVING AVG(score) > 85;
9.5 示例:至少考了 3 门课的学生
SELECT student_id AS 学号, COUNT(*) AS 科目数
FROM scores
GROUP BY student_id
HAVING COUNT(*) >= 3;
9.6 HAVING 里用别名(MySQL 支持)
在 MySQL 里,SELECT 里起的别名可以在 HAVING 里用:
SELECT class_name AS 班级, COUNT(*) AS 人数
FROM students
GROUP BY class_name
HAVING 人数 > 2;
但为了兼容其他数据库,习惯上 HAVING 里直接写 *COUNT() 或 AVG(score)** 更常见。
10. WHERE、GROUP BY、HAVING、ORDER BY 的顺序
10.1 书写与执行顺序
书写顺序(必须遵守):
SELECT ...
FROM 表名
WHERE 条件 -- 1. 先筛行
GROUP BY 列 -- 2. 再分组
HAVING 条件 -- 3. 再筛组
ORDER BY 列 -- 4. 再排序
LIMIT n; -- 5. 再限制条数
执行顺序可以理解为:FROM → WHERE → GROUP BY → 聚合 → HAVING → SELECT → ORDER BY → LIMIT。
10.2 综合示例:先筛行、再分组、再筛组、再排序
查“高一1班和高一2班中,人数不少于 2 的班级及其人数,按人数降序”:
SELECT class_name AS 班级, COUNT(*) AS 人数
FROM students
WHERE class_name IN ('高一1班', '高一2班')
GROUP BY class_name
HAVING COUNT(*) >= 2
ORDER BY 人数 DESC;
11. 多列分组与常见用法
11.1 按多列分组
GROUP BY 列1, 列2 表示:列1 和 列2 都相同的行为一组。
示例:每个班级、每种性别人数
SELECT class_name AS 班级, gender AS 性别, COUNT(*) AS 人数
FROM students
GROUP BY class_name, gender;
结果会类似:高一1班 男 2、高一1班 女 0、高一2班 男 0、高一2班 女 3……(按你实际数据)。
11.2 分组 + 多列聚合
示例:每个学生、每门课的最高分(若同一学生同一科目有多条,取最大;这里每人每科一条,演示写法)
SELECT student_id, subject, MAX(score) AS 分数
FROM scores
GROUP BY student_id, subject;
11.3 与 ORDER BY 配合
分组结果也可以排序:
SELECT class_name AS 班级, COUNT(*) AS 人数
FROM students
GROUP BY class_name
ORDER BY 人数 DESC;
12. 常见错误与注意点
12.1 SELECT 了非分组列且非聚合列
在“严格”或“ONLY_FULL_GROUP_BY”模式下,SELECT 的列必须要么在 GROUP BY 里,要么被聚合函数包起来。
错误示例:SELECT name, class_name, COUNT(*) FROM students GROUP BY class_name;(name 既不在 GROUP BY 里也不是聚合),会报错。
正确:只 SELECT 分组列 + 聚合,或把 name 也放进 GROUP BY(但同一班多人会多行)。
12.2 把 HAVING 当 WHERE 用
- 过滤行用 WHERE(在 GROUP BY 前)。
- 过滤组用 HAVING(在 GROUP BY 后,条件里常用聚合函数)。
若条件只涉及“普通列”、不涉及“组的结果”,应放 WHERE,例如“只统计高一1班”用 WHERE class_name = ‘高一1班’,不要用 HAVING class_name = ‘高一1班’(虽然有时结果对,但语义不对,且可能影响性能)。
12.3 聚合函数不能直接写进 WHERE
WHERE 在分组前执行,此时还没有“组”和“组的结果”,所以 WHERE 里不能写聚合函数。
例如“平均分大于 85 的学生”必须用 GROUP BY + HAVING AVG(score) > 85,不能写 WHERE AVG(score) > 85。
12.4 GROUP BY 的列里有 NULL
多行在分组列上都是 NULL 时,会归为同一组,结果里该组的分组列显示为 NULL。这是正常行为。
13. 综合示例与速查表
13.1 综合示例(可整段执行)
USE school;
-- 1. 总人数
SELECT COUNT(*) AS 学生总数 FROM students;
-- 2. 每个班级人数
SELECT class_name AS 班级, COUNT(*) AS 人数
FROM students
GROUP BY class_name;
-- 3. 每个班级平均年龄
SELECT class_name AS 班级, AVG(age) AS 平均年龄
FROM students
GROUP BY class_name;
-- 4. 人数 >= 2 的班级
SELECT class_name AS 班级, COUNT(*) AS 人数
FROM students
GROUP BY class_name
HAVING COUNT(*) >= 2;
-- 5. 每个学生总分、平均分
SELECT student_id AS 学号, SUM(score) AS 总分, AVG(score) AS 平均分
FROM scores
GROUP BY student_id;
-- 6. 平均分 > 85 的学生
SELECT student_id AS 学号, AVG(score) AS 平均分
FROM scores
GROUP BY student_id
HAVING AVG(score) > 85;
-- 7. 每科最高分、最低分、平均分
SELECT subject AS 科目, MAX(score) AS 最高, MIN(score) AS 最低, AVG(score) AS 平均
FROM scores
GROUP BY subject;
-- 8. WHERE + GROUP BY + HAVING + ORDER BY
SELECT class_name AS 班级, COUNT(*) AS 人数
FROM students
WHERE age >= 17
GROUP BY class_name
HAVING COUNT(*) >= 1
ORDER BY 人数 DESC;
13.2 聚合与分组速查表
| 需求 | 写法示例 |
|---|---|
| 总行数 | SELECT COUNT(*) FROM 表; |
| 某列非 NULL 数 | SELECT COUNT(列) FROM 表; |
| 某列去重个数 | SELECT COUNT(DISTINCT 列) FROM 表; |
| 求和 | SELECT SUM(列) FROM 表; |
| 平均 | SELECT AVG(列) FROM 表; |
| 最大/最小 | SELECT MAX(列), MIN(列) FROM 表; |
| 按某列分组计数 | SELECT 列, COUNT(*) FROM 表 GROUP BY 列; |
| 按某列分组聚合 | SELECT 列, AVG(数值列) FROM 表 GROUP BY 列; |
| 分组后过滤 | ... GROUP BY 列 HAVING 聚合函数 条件; |
子句顺序:WHERE → GROUP BY → HAVING → ORDER BY → LIMIT。
把聚合函数和 GROUP BY、HAVING 练熟后,你就能做“每类有多少、每类总和/平均/最大最小、再按聚合结果筛选”这类统计。建议用文档里的 students 和 scores 把上面的示例都跑一遍,再自己改分组列和 HAVING 条件多试几种,巩固聚合与分组。