在数据库开发中,我们经常需要从多个表中获取数据来组成完整的信息。比如想知道某个学生的姓名、班级名称、各科成绩,这些数据可能分散在学生表、班级表和成绩表中。这时候,联合查询就成了必备技能。本文将从底层原理到实战案例,全面讲解 MySQL 联合查询的核心知识。
一、为什么需要联合查询?
数据库设计遵循范式原则,目的是减少数据冗余。这意味着一个完整的业务信息会被拆分到多个表中:
- 学生的基本信息存在
student
表(姓名、学号、班级编号等) - 班级信息存在
class
表(班级编号、班级名称等) - 成绩信息存在
score
表(学生 ID、课程 ID、分数等)
要获取 "学生姓名 + 班级名称 + 成绩" 这样的完整信息,就必须将多个表 "联合" 起来查询 —— 这就是联合查询的核心价值。
二、MySQL 联合查询的底层逻辑
你知道 MySQL 是如何处理多表查询的吗?核心步骤只有两步:
-
计算笛卡尔积:将参与查询的所有表进行组合,生成一张临时表。例如学生表(3 条数据)和班级表(3 条数据)的笛卡尔积会产生 3×3=9 条数据(见下表)。
学生表。编号 姓名 班级编号 班级表。编号 班级名称 1 张三 1 1 java113 1 张三 1 2 java78 1 张三 1 3 C++110 ... ... ... ... ... -
过滤无效数据:通过表之间的关联关系(如
student.班级编号 = class.编号
),从笛卡尔积中筛选出有效数据。
三、核心连接方式:内连接、外连接、自连接
3.1 内连接(INNER JOIN):只取匹配的数据
内连接是最常用的连接方式,只返回两个表中满足连接条件的记录。
语法(两种方式):
-- 方式1:逗号分隔表,where指定连接条件
select 字段 from 表1, 表2 where 表1.关联字段 = 表2.关联字段;
-- 方式2:JOIN关键字,on指定连接条件(推荐,更清晰)
select 字段 from 表1 [inner] join 表2 on 表1.关联字段 = 表2.关联字段;
示例:查询 "唐三藏" 的成绩
-- 关联学生表(student)和成绩表(score)
select s.name, sc.score
from student s
join score sc on sc.student_id = s.id
where s.name = '唐三藏';
结果会返回唐三藏的所有成绩记录:
name | score |
---|---|
唐三藏 | 70.5 |
唐三藏 | 98.5 |
唐三藏 | 33 |
唐三藏 | 98 |
3.2 外连接:保留不匹配的数据
内连接只返回匹配的记录,但有时我们需要保留某张表的所有记录(即使没有匹配)。外连接分为两种:
- 左外连接(LEFT JOIN):保留左表所有记录,右表无匹配则显示 NULL。
- 右外连接(RIGHT JOIN):保留右表所有记录,左表无匹配则显示 NULL。
注意:MySQL 不支持全外连接(FULL JOIN),但可通过
LEFT JOIN + UNION + RIGHT JOIN
模拟。
示例 1:查询所有学生的考试记录(包括没参加考试的)
-- 左表为student,右表为score,保留所有学生
select s.name, sc.score
from student s
left join score sc on s.id = sc.student_id;
结果中,"不想毕业" 同学因未参加考试,score 字段为 NULL:
name | score |
---|---|
唐三藏 | 70.5 |
... | ... |
不想毕业 | NULL |
示例 2:查询所有班级(包括没有学生的)
-- 右表为class,保留所有班级
select c.name, s.name
from student s
right join class c on s.class_id = c.id;
结果中,"前端 001 班" 因没有学生,student.name 为 NULL:
班级名称 | name |
---|---|
Java001 班 | 唐三藏 |
C++001 班 | 宋江 |
前端 001 班 | NULL |
3.3 自连接:表与自身的连接
自连接是一种特殊的连接方式 ——表自己和自己连接,常用于 "行与行之间的比较"。使用时必须给表起不同的别名。
示例:查询 MySQL 成绩比 Java 高的学生
-- 成绩表score分别用s1(MySQL)和s2(Java)作为别名
select s1.student_id, s1.score as mysql_score, s2.score as java_score
from score s1, score s2
where s1.student_id = s2.student_id -- 同一学生
and s1.course_id = 3 -- MySQL的课程ID
and s2.course_id = 1 -- Java的课程ID
and s1.score > s2.score;
结果:
student_id | mysql_score | java_score |
---|---|---|
1 | 98.5 | 70.5 |
3 | 68 | 33 |
四、子查询:嵌套在查询中的查询
子查询(嵌套查询)是将一个查询的结果作为另一个查询的条件或数据源。根据返回结果的行数 / 列数,可分为:
- 单行子查询:返回 1 行 1 列,用
=
、>
等比较符。 - 多行子查询:返回多行 1 列,用
IN
、NOT IN
等。 - 多列子查询:返回多行多列,用
(col1, col2) IN (...)
。
示例 1:查询 "不想毕业" 的同班同学(单行子查询)
-- 内层查询获取班级ID,外层查询用=匹配
select name
from student
where class_id = (select class_id from student where name = '不想毕业');
示例 2:查询 Java 或 MySQL 课程的成绩(多行子查询)
-- 内层查询返回多个课程ID,外层用IN匹配
select * from score
where course_id in (select id from course where name in ('Java', 'MySQL'));
五、合并查询:UNION 与 UNION ALL
当需要合并两个查询的结果时,可使用UNION
或UNION ALL
:
- UNION:合并结果并自动去重。
- UNION ALL:合并结果但保留重复行(效率更高)。
示例:合并两个学生表的数据
-- 合并student表(id<3)和student1表,自动去重
select * from student where id < 3
union
select * from student1;
六、实用技巧:插入查询结果与创建表
联合查询的结果不仅可以直接查看,还能用于插入数据或创建新表:
插入查询结果:
-- 将C++001班的学生复制到student1表
insert into student1 (name, sno, class_id)
select s.name, s.sno, s.class_id
from student s, class c
where s.class_id = c.id and c.name = 'C++001班';
根据查询结果创建表:
-- 将查询结果保存为新表
create table java_students as
select s.name, c.name as class_name
from student s, class c
where s.class_id = c.id and c.name = 'Java001班';
总结
联合查询是 MySQL 中处理多表数据的核心技术,掌握以下要点将大幅提升你的数据处理能力:
- 内连接用于获取两表匹配的数据;
- 外连接用于保留某一表的所有记录;
- 自连接用于行与行的比较;
- 子查询让复杂逻辑分步实现;
- UNION 用于合并多个查询结果。
实际开发中,需根据业务场景选择合适的查询方式,并注意通过索引优化连接效率(尤其是多表连接时)。