【零基础入门】MySQL 多表操作全解析:外键约束 + 多表查询 + 高级语法
·
一、前言
在单表操作(DDL/DML/DQL)的基础上,多表操作是 MySQL 进阶的核心,也是实际开发中处理复杂业务数据的必备技能。本文将从 “多表关系约束” 到 “多表查询”,再到 “高级语法”,全方位拆解 MySQL 多表操作的核心知识点:涵盖一对多外键约束、交叉连接、内连接、外连接、子查询、CASE WHEN 等,结合学生成绩 / 班级管理实战案例(与前文知识点关联),让零基础的你也能轻松掌握!
二、多表关系基础:外键约束(FOREIGN KEY)
2.1 核心概念
- 主表:拥有主键的表(一的一方,如班级表);
- 从表(外表):拥有外键的表(多的一方,如学生表);
- 外键约束:保证从表的外键列值必须是主表主键列已存在的值,避免脏数据,维护多表数据一致性。
2.2 一对多关系建表原则
在 “多” 的一方新增一列作为外键,关联 “一” 的一方的主键(如学生表的class_id关联班级表的id,与前文学生成绩表场景呼应)。
2.3 外键约束实战(班级 - 学生案例)
2.3.1 语法格式
表格
| 操作 | 语法 |
|---|---|
| 建表时添加外键 | CREATE TABLE 从表(列定义..., CONSTRAINT 外键名 FOREIGN KEY(外键列) REFERENCES 主表(主键列)); |
| 建表后添加外键 | ALTER TABLE 从表 ADD CONSTRAINT 外键名 FOREIGN KEY(外键列) REFERENCES 主表(主键列); |
| 删除外键 | ALTER TABLE 从表 DROP FOREIGN KEY 外键名; |
2.3.2 实操案例
-- 切换数据库(与前文学生成绩表共用day02库)
USE day02;
-- 1. 创建主表:班级表(一的一方,对应前文student_score表的class字段)
CREATE TABLE class_info(
id INT PRIMARY KEY AUTO_INCREMENT, -- 班级ID(主键)
class_name VARCHAR(20) -- 班级名称(如一班、二班、三班)
);
-- 2. 创建从表:学生信息表(多的一方,添加外键约束)
CREATE TABLE student_info(
id INT PRIMARY KEY AUTO_INCREMENT, -- 学生ID(主键)
stu_name VARCHAR(20), -- 学生姓名
stu_age INT, -- 学生年龄
class_id INT, -- 外键列:关联班级表ID
-- 建表时添加外键约束(推荐命名规范:fk_主表名_从表名)
CONSTRAINT fk_class_student FOREIGN KEY(class_id) REFERENCES class_info(id)
);
-- 3. 插入主表数据(班级表,与前文student_score表的班级一致)
INSERT INTO class_info VALUES(null, '一班'), (null, '二班'), (null, '三班'), (null, '四班');
-- 4. 插入从表数据(学生表,沿用前文学生姓名)
INSERT INTO student_info VALUES(null, '张三', 18, 1); -- 合法:class_id=1(一班)存在
INSERT INTO student_info VALUES(null, '李四', 19, 1); -- 合法:class_id=1(一班)存在
INSERT INTO student_info VALUES(null, '王五', 18, 2); -- 合法:class_id=2(二班)存在
INSERT INTO student_info VALUES(null, '赵六', 20, 2); -- 合法:class_id=2(二班)存在
-- INSERT INTO student_info VALUES(null, '钱七', 19, 10); -- 非法:class_id=10不存在于班级表
-- 5. 查看数据(关联前文学生成绩表场景)
SELECT * FROM class_info;
SELECT * FROM student_info;
-- 6. 删除外键约束
ALTER TABLE student_info DROP FOREIGN KEY fk_class_student;
-- 7. 建表后添加外键(前提:从表外键列数据均合法)
ALTER TABLE student_info ADD FOREIGN KEY(class_id) REFERENCES class_info(id);
2.3.3 注意事项
- 实际开发中,外键约束使用较少(会降低插入 / 删除效率),更多通过代码层面控制数据一致性;
- 外键列的数据类型必须与主表主键列一致;
- 删除主表数据前,需先删除从表关联数据(或设置外键级联操作);
- 本案例与前文
student_score表的班级字段完全对应,可联动查询学生成绩 + 班级信息。
三、多表查询核心:连接查询
3.1 环境准备(学生 - 成绩案例,关联前文)
-- 1. 复用前文创建的student_score表(学生成绩表)
-- 若未创建,执行以下语句(与前文保持一致)
CREATE TABLE IF NOT EXISTS student_score(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
age INT,
gender CHAR(1),
score DOUBLE,
class VARCHAR(20)
);
INSERT INTO student_score(id,name,age,gender,score,class) VALUES
(null,'张三',18,'男',88,'一班'),
(null,'李四',19,'男',92,'一班'),
(null,'王五',18,'男',65,'二班'),
(null,'赵六',20,'女',78,'二班'),
(null,'钱七',19,'女',95,'三班'),
(null,'孙八',18,'男',58,'三班'),
(null,'周九',19,'女',82,'一班'),
(null,'吴十',20,'男',45,'二班'),
(null,'郑十一',18,'女',90,'三班'),
(null,'王十二',19,'男',73,'一班');
-- 2. 班级表已在2.3.2中创建,补充关联字段(便于多表查询)
ALTER TABLE student_score ADD COLUMN class_id INT;
-- 更新student_score的class_id(关联class_info表)
UPDATE student_score SET class_id = 1 WHERE class = '一班';
UPDATE student_score SET class_id = 2 WHERE class = '二班';
UPDATE student_score SET class_id = 3 WHERE class = '三班';
-- 查看关联后的数据
SELECT * FROM student_score;
SELECT * FROM class_info;
3.2 交叉连接(CROSS JOIN)
3.2.1 核心说明
- 结果:两张表的笛卡尔积(表 A 行数 × 表 B 行数),产生大量脏数据;
- 用途:几乎不用于实际开发,仅用于理解连接查询底层逻辑。
3.2.2 语法 & 案例
-- 格式1:逗号分隔
SELECT * FROM student_score, class_info;
-- 格式2:JOIN关键字
SELECT * FROM student_score JOIN class_info;
3.3 内连接(INNER JOIN)★★★
3.3.1 核心说明
- 结果:两张表的交集(仅返回满足关联条件的数据);
- 场景:查询 “有关联关系” 的数据(如学生成绩 + 对应班级名称)。
3.3.2 语法 & 案例
表格
| 类型 | 语法 | 说明 |
|---|---|---|
| 隐式内连接 | SELECT * FROM 表1, 表2 WHERE 关联条件; |
简洁但可读性差 |
| 显式内连接 | SELECT * FROM 表1 INNER JOIN 表2 ON 关联条件; |
推荐(INNER 可省略),可读性 / 效率更高 |
-- 1. 隐式内连接(查询学生成绩+对应班级名称)
SELECT ss.name, ss.score, ci.class_name
FROM student_score ss, class_info ci
WHERE ss.class_id = ci.id;
-- 简化写法(无重名字段时)
SELECT name, score, class_name
FROM student_score, class_info
WHERE class_id = id;
-- 2. 显式内连接(推荐)
SELECT ss.name AS 学生姓名, ss.score AS 考试分数, ci.class_name AS 班级名称
FROM student_score ss INNER JOIN class_info ci
ON ss.class_id = ci.id;
-- 省略INNER(效果一致)
SELECT ss.name AS 学生姓名, ss.score AS 考试分数, ci.class_name AS 班级名称
FROM student_score ss JOIN class_info ci
ON ss.class_id = ci.id;
3.4 外连接(OUTER JOIN)★★★
3.4.1 核心说明
表格
| 类型 | 结果 | 场景 |
|---|---|---|
| 左外连接(LEFT JOIN) | 左表全集 + 两表交集 | 需保留左表所有数据(如查询所有学生成绩,即使无对应班级) |
| 右外连接(RIGHT JOIN) | 右表全集 + 两表交集 | 需保留右表所有数据(如查询所有班级,即使无对应学生) |
| 满外连接(FULL JOIN) | 左表全集 + 右表全集 + 交集 | MySQL 不直接支持,需用 UNION 合并左右外连接 |
3.4.2 语法 & 案例
-- 1. 左外连接(OUTER可省略)
-- 查询所有学生成绩,无对应班级则显示NULL(本案例无NULL,仅演示语法)
SELECT ss.name AS 学生姓名, ss.score AS 考试分数, ci.class_name AS 班级名称
FROM student_score ss LEFT OUTER JOIN class_info ci
ON ss.class_id = ci.id;
-- 省略OUTER(推荐)
SELECT ss.name AS 学生姓名, ss.score AS 考试分数, ci.class_name AS 班级名称
FROM student_score ss LEFT JOIN class_info ci
ON ss.class_id = ci.id;
-- 2. 右外连接(OUTER可省略)
-- 查询所有班级,无对应学生则显示NULL(如四班无学生)
SELECT ss.name AS 学生姓名, ss.score AS 考试分数, ci.class_name AS 班级名称
FROM student_score ss RIGHT OUTER JOIN class_info ci
ON ss.class_id = ci.id;
-- 省略OUTER(推荐)
SELECT ss.name AS 学生姓名, ss.score AS 考试分数, ci.class_name AS 班级名称
FROM student_score ss RIGHT JOIN class_info ci
ON ss.class_id = ci.id;
-- 3. 满外连接(MySQL兼容写法)
-- UNION:合并并去重;UNION ALL:合并不去重(效率更高)
SELECT ss.name AS 学生姓名, ss.score AS 考试分数, ci.class_name AS 班级名称
FROM student_score ss LEFT JOIN class_info ci ON ss.class_id = ci.id
UNION ALL
SELECT ss.name AS 学生姓名, ss.score AS 考试分数, ci.class_name AS 班级名称
FROM student_score ss RIGHT JOIN class_info ci ON ss.class_id = ci.id;
四、高级查询语法
4.1 子查询
4.1.1 核心概念
- 定义:一个 SQL 的查询条件依赖另一个 SQL 的结果(内层为子查询,外层为主查询);
- 场景:分步查询的逻辑合并(如 “查询每个班级的最高分学生”,关联前文聚合查询知识点)。
4.1.2 语法 & 案例
-- 需求:查询三班分数最高的学生信息(关联前文student_score表)
-- 分步实现
-- Step1:查询三班的最高分数
SELECT MAX(score) FROM student_score WHERE class = '三班';
-- Step2:根据最高分数+班级查学生
SELECT * FROM student_score WHERE class = '三班' AND score = (SELECT MAX(score) FROM student_score WHERE class = '三班');
-- 实际开发优化写法(连接查询替代子查询,效率更高)
SELECT ss.*
FROM student_score ss
JOIN (SELECT class, MAX(score) AS max_score FROM student_score GROUP BY class) t1
ON ss.class = t1.class AND ss.score = t1.max_score
WHERE ss.class = '三班'; -- 筛选三班
4.2 CASE WHEN 条件语法 ★★★
4.2.1 核心说明
- 作用:实现 “行转列” 或 “条件赋值”(类似编程语言的 if-else);
- 场景:将分数转换为等级(与前文学生成绩表强关联)。
4.2.2 语法格式
表格
| 格式 | 语法 | 适用场景 |
|---|---|---|
| 通用格式 | CASE WHEN 条件1 THEN 结果1 WHEN 条件2 THEN 结果2 ELSE 结果n END [AS 别名]; |
任意条件判断 |
| 简化格式 | CASE 字段名 WHEN 值1 THEN 结果1 WHEN 值2 THEN 结果2 ELSE 结果n END [AS 别名]; |
同一字段的等于判断 |
4.2.3 实操案例(学生成绩等级划分)
-- 需求:将学生分数转换为等级(<60→不及格,60-80→及格,80-90→良好,≥90→优秀)
-- 通用格式(推荐,兼容性强)
SELECT
name AS 学生姓名,
score AS 考试分数,
class AS 班级,
CASE
WHEN score < 60 THEN '不及格'
WHEN score >= 60 AND score < 80 THEN '及格'
WHEN score >= 80 AND score < 90 THEN '良好'
WHEN score >= 90 THEN '优秀'
ELSE '无成绩'
END AS 成绩等级
FROM student_score;
-- 拓展:结合班级表,查询学生姓名+班级名称+成绩等级
SELECT
ss.name AS 学生姓名,
ci.class_name AS 班级名称,
ss.score AS 考试分数,
CASE
WHEN ss.score < 60 THEN '不及格'
WHEN ss.score >= 60 AND ss.score < 80 THEN '及格'
WHEN ss.score >= 80 AND ss.score < 90 THEN '良好'
WHEN ss.score >= 90 THEN '优秀'
ELSE '无成绩'
END AS 成绩等级
FROM student_score ss
JOIN class_info ci ON ss.class_id = ci.id;
4.3 拓展知识点(进阶方向)
4.3.1 自关联查询
- 定义:表自身与自身连接查询(如班级表扩展:年级→班级→小组);
- 语法:
SELECT * FROM class_info t1 JOIN class_info t2 ON t1.id = t2.parent_id;
4.3.2 窗口函数
- 作用:实现分组内排序、排名、累加等(如每个班级内成绩排名);
- 示例:
SELECT
name AS 学生姓名,
class AS 班级,
score AS 考试分数,
RANK() OVER(PARTITION BY class ORDER BY score DESC) AS 班级内排名
FROM student_score;
五、实操避坑指南
- 外键约束陷阱:MySQL 中删除主表数据前,需先删除从表关联数据,否则报错;
- 连接查询别名:多表查询时务必给表加别名(如 ss 代表 student_score),避免字段冲突;
- NULL 值处理:外连接结果可能包含 NULL,需用 IFNULL () 处理(如
IFNULL(ci.class_name, '未分配班级')); - 子查询效率:复杂场景优先用连接查询替代子查询(减少数据库扫描次数);
- UNION vs UNION ALL:无需去重时用 UNION ALL(效率远高于 UNION);
- 关联字段一致性:多表查询时确保关联字段(如 class_id)数据类型 / 值一致,否则无匹配结果。
六、总结
核心要点回顾
- 多表关系中,一对多需在 “多” 的一方加外键,本案例将班级表与前文学生成绩表关联,贴合实际业务场景;
- 多表查询核心:内连接查交集(学生 + 对应班级),左 / 右外连接查 “全集 + 交集”,满外连接需用 UNION 合并;
- 子查询适合简单分步逻辑,复杂场景优先用连接查询(如每个班级最高分学生);
- CASE WHEN 可实现成绩等级划分等行级条件判断,是业务报表的常用语法;
- 所有案例均沿用前文学生成绩表的基础数据,知识点前后联动,便于系统掌握。
进阶学习建议
- 练习经典场景:如 “查询每个班级的及格率”“查询成绩优秀的学生所属班级”;
- 学习性能优化:索引对多表查询的影响、EXPLAIN 分析执行计划;
- 拓展多表关系:多对多(如学生 - 课程表,需中间表)、一对一(如学生 - 学籍表)。
更多推荐




所有评论(0)