本文主要讨论MySQL中的聚合查询、分组查询和联合查询。
聚合查询
|--------------------------|-----------------------|
| 函数 | 说明 |
| COUNT([DISTINCT] expr) | 返回查询到的数据的数量 |
| SUM([DISTINCT] expr) | 返回查询到的数据的总和,不是数字没有意义 |
| AVG([DISTINCT] expr) | 返回查询到的数据的平均值,不是数字没有意义 |
| MAX([DISTINCT] expr) | 返回查询到的数据的最大值,不是数字没有意义 |
| MIN([DISTINCT] expr) | 返回查询到的数据的最小值,不是数字没有意义 |
聚合查询本质上是针对数据中行和行之间的运算。
1、count

查询记录的行数。在()中也可以填写常量,效果和*号相同;也可以填指定列的字段;不过,使用指定字段时不会统计值为NULL的数据,只有*才能统计全部数据。
2、sum
就是用于求和。例如求学生表中学生语文成绩的总和:
sql
select sum(chinese) from exam;
3、avg
和上面的使用方法一样,用于求平均值。例如求英语成绩的平均值:
sql
select avg(english) from exam;
聚合函数也可以对表达式进行运算。比如:
sql
select avg(chinese + english + math) from exam;
4、max、min
用于求最大,最小值:
sql
select max(chinese), min(english) from exam;
分组查询
分组查询为把相同类型的数据归为一组,然后统计每组的结果;可以把数据按某个字段归类,每组只返回一行统计结果。
GROUP BY子句
select中使用GROUP BY子句可以对指定列进行分组查询。在分组查询时,select指定的字段必须是"分组依据字段",其他字段若想出现在select中则必须包含在聚合函数中。
语法如下:
sql
select column1, sum(column2), ...from table group by column1 ... ;
其中,column1表示需要分组的列名;column2表示没有被分组的列(需要运算的列),使用聚合函数;
例如,现在有一张学生表,需要查询每个班有多少个人:
sql
SELECT
class, -- 分组字段:按班级分
COUNT(*) AS 人数 -- 聚合函数:统计每组数量
FROM student
GROUP BY class; -- 核心:按class分组
结果如下:

COUNT 能统计行数。
HAVING子句
having 语句和 where 语句的作用都是过滤,不过where是过滤分组之前的数据;而having是对分组之后的数据进行过滤:
sql
SELECT class, AVG(score)
FROM student
WHERE score >= 60 -- 先过滤掉不及格的原始记录
GROUP BY class
HAVING AVG(score) >= 80; -- 再过滤平均分低于80的班级
联合查询
联合查询表示联合多个表进行查询;将多条select查询的结果,上下拼接成一个结果集。
笛卡尔积
笛卡尔积方法是将两个集合中所有元素两两组合,例如下面两张表:


通过select语句可以对两张表做笛卡尔积操作,操作后的表为:

此时可以看到直接将两张表的数据合并。
代码书写如下:
sql
select * from student, class;
当然我们也可以选择指定的列:
sql
select student.name, class.name from student, class;
如果觉得过于冗长,也可以使用起别名的方式简化:
sql
select s.name, c.name from student s, class c;
但是在实际工作中,笛卡尔积方法数据量巨大,因此我们更多使用的是假过滤条件合并。
where过滤
想想这样一个场景,现在有一个学生表和一个班级表,两张表中都存储了班号,现在我们需要将两张表中班号相同的同学过滤出来并整合成一个新的表;如果我们使用的是select语句,那么书写如下:
sql
select * from student, class where student.class_id = class.class_id;
这样联合两个或者多个表的方式就为联合查询。联合查询又被叫做表连接查询:
- 确定哪些表需要参与查询
- 根据表与表之间的主键外键关系,确定过滤条件
- 精确查询字段,得到想要的结果
内连接(JOIN)
内连接是数据库多表查询中最常用的连接方式,核心作用是:只返回多个表中满足 "关联条件" 的行,不满足条件的行会被直接过滤掉。
该方法可以理解为 "取多个表的交集",也是避免笛卡尔积的核心手段。内连接的本质就为上面介绍的"笛卡尔积 + where 过滤"。
sql
SELECT 列名 FROM 表1, 表2 WHERE 表1.关联列 = 表2.关联列;
这种写法就是我们上面where过滤的写法;不过,对于这种内连接的方式,我们还有另外一种或标准的写法:
sql
select 列名
from 表1
inner join 表2 on 表1.关联列 = 表2.关联列
-- 可选:加WHERE过滤行
where 过滤条件;
以上两种方法都能实现内连接。更详细的操作步骤如下:
- 确定查询中涉及到哪些表
- 对目标取笛卡尔积
- 确定连接条件
- 确定对整个结果集的过滤条件
- 精确查询字段
外连接(LEFT/RIGHT)
外连接分为左外连接和右外连接;如果联合查询,左侧的表完全显示就是左外连接;右侧的表完全显示就是右外连接。如果没有对应的数据就会补NULL。
我们来看左外连接;假设现在有两张表,一张学生表,一张成绩表,我们使用如下的代码:
sql
SELECT s.name, sc.score
FROM student s
LEFT JOIN score sc
ON s.id = sc.stu_id;
表示查询所有学生,包括没有成绩的学生;无成绩的学生的成绩会显示为NULL。这就是左外连接。
右外连接与左外连接道理相同。例如:
sql
SELECT s.name, sc.score
FROM student s
RIGHT JOIN score sc
ON s.id = sc.stu_id;
表示查询所有成绩,包括没有对应学生的成绩。
可以看到,在 join 之前的表就为"左表";而join之后的表就为"右表"。
除了左右连接外,还有全外连接,会保留两张表的所有数据,并且不匹配的字段全部补NULL。不过MySQL中不支持全外连接,这里就不过多介绍。
自连接
顾名思义,自连接就是自己和自己连接。即把同一张表当成两张不同的表来关联查询,核心用途是:查询表中具有层级 / 关联关系的数据。
我们用一个例子来理解。假如现在有一张员工表:
|----|------|------------|
| id | name | manager_id |
| 1 | 张三 | NULL |
| 2 | 李四 | 1 |
| 3 | 王五 | 1 |
| 4 | 赵六 | 2 |
在该表中,manager_id表示该员工指向的上级id。例如李四的manager_id指向1,而1为张三的id,说明张三是李四的上级。
现在,我们需要直接查询每个员工的名字和上级的名字并显示出来,这时就需要自连接。
自连接没有自己特殊的写法,而是通过左右连接或内连接来实现。不过需要将表起两个别名。例如:
sql
-- 给同一张表起两个别名:e(员工)、m(上级/管理者)
SELECT
e.name AS 员工名,
m.name AS 上级名
FROM employee e
LEFT JOIN employee m
ON e.manager_id = m.id;
这样就能实现自连接。自连接的两种写法如下:
sql
--左连接
SELECT *
FROM 表名 t1
LEFT JOIN 表名 t2
ON t1.字段 = t2.字段;
--内连接
SELECT *
FROM 表名 t1, 表名 t2
WHERE t1.字段 = t2.字段;
子查询
子查询又叫嵌套查询。
具体来说就是把一个完整的 SELECT 查询,嵌套在另一个 SELECT / INSERT / UPDATE / DELETE 里面,这个内层查询就叫子查询。
其中,外层查询叫主查询;内层查询叫子查询。
子查询就是把一条sql的查询结果,当做另一条sql的查询条件,可以嵌套很多层。
示例
我们用一个简单的例子来理解:假设现在要找出成绩大于90分的学生的名字,按照我们前面学习的方法,查询代码为:
sql
SELECT s.name FROM student s
JOIN score sc ON s.id=sc.stu_id
WHERE sc.score > 90;
而使用子查询的代码如下:
sql
SELECT name
FROM student
WHERE id IN (
-- 子查询:先查出成绩>90的学生id
SELECT stu_id FROM score WHERE score > 90
);
可以看到子查询代码的执行顺序为 先运行括号内的子查询,得到学生id 后将该查询结果传给外层查询使用 从而实现目标。
由于嵌套的层级是没有固定的限制的,如果嵌套过多则查询效率是不可控的,在实际操作中需要谨慎使用。
标量子查询
该子查询方式返回一行一列的标量结果。例如:
sql
-- 找出成绩高于平均分的学生
SELECT * FROM score
WHERE score > (
SELECT AVG(score) FROM score -- 子查询只返回1个平均值
);
这样子查询会返回一个平均值。
列子查询
结果为返回一列数据。例如:
sql
-- 在A班的所有学生
SELECT * FROM student
WHERE class_id IN (
SELECT id FROM class WHERE name='A班'
);
必须使用关键字 IN/NOT IN/ANY/ALL。
IN:只要匹配成功一个即可的
sql--查询是否为初一的学生 SELECT * FROM student WHERE class_id IN (SELECT id FROM class WHERE grade='初一');NOT IN:全都不匹配,不在这组结果里的
sql-- 查询不在 1班、2班 的学生 SELECT * FROM student WHERE class_id NOT IN (1,2);ANY:和子查询中任意一个值满足条件的
sql-- 查询只要比子查询里任意一个数学分数高就行(比最低分高) SELECT * FROM score WHERE score > ANY (SELECT score FROM score WHERE subject='数学');ALL:和子查询结果中的所有值都满足的
sql-- 查询比所有数学成绩都高(最高分) SELECT * FROM score WHERE score > ALL (SELECT score FROM score WHERE subject='数学');
表子查询
把子查询的结果当做一张临时表,这里必须起别名。例如:
sql
-- 查询最高分学生信息
SELECT * FROM (
SELECT * FROM score ORDER BY score DESC LIMIT 2
) AS top_students; -- 子查询结果当表用
合并查询(UNION)
合并查询 = 把多个 SELECT 查询的结果,上下拼接成一张表。
这里和JOIN查询的方法不同,JOIN方法联合两张表让列增多;而UNION则是联合两张表让行增多。即:
- JOIN 是左右拼接(列变多)
- UNION 是上下拼接(行变多)
合并查询有两个语法:
UNION:合并且自动去重,会去掉重复行的数据;
UNION ALL:直接合并,保留所有数据,不会自动去重。
如果要使用合并查询,要满足以下三个条件:
- 两个查询的列数必须相同
- 对应列的数据类型必须一致
- 列名可以不同,但结果以第一个查询的列名为准
示例如下:
sql
-- 查询成绩>90 或 <60 的学生(不用 OR,用 UNION 更清晰)
SELECT name, score FROM student WHERE score > 90
UNION ALL
SELECT name, score FROM student WHERE score < 60;