MySQL 多表联合查询与数据备份恢复全攻略
作者:Sadsvit
MySQL 多表联合查询与数据备份恢复全指南
在关系型数据库中,表与表通过外键等关联字段建立联系,实际业务场景常需同时查询多个表的关联数据,这就需要多表联合查询。此外,数据安全是数据库管理的核心,定期备份与高效恢复是保障数据不丢失的关键。本文将系统讲解 MySQL 多表联合查询的5种核心方式(交叉连接、内连接、外连接、分组查询、子查询),并详细说明数据库备份与恢复的完整流程,包含实战案例与效果验证。
一、多表联合查询基础
1. 什么是多表联合查询
前面讲解的查询语句均针对单个表,但关系型数据库中表与表存在业务关联(如“学生表”与“课程表”通过“课程ID”关联),多表联合查询即同时查询两个或两个以上的表,通过关联条件提取整合数据,满足复杂业务需求(如“查询学生姓名及对应课程名称”)。
在 MySQL 中,多表查询主要分为 交叉连接、内连接、外连接、分组查询、子查询 5种,核心是通过“关联条件”消除无效数据,确保查询结果的准确性与实用性。
二、多表联合查询的5种方式
1. 交叉连接(CROSS JOIN)——笛卡尔积查询
交叉连接是最基础的多表查询方式,分为显式交叉连接和隐式交叉连接,核心是返回两张表的“笛卡尔积”,但实际应用中需谨慎使用(易产生大量无效数据)。
(1)核心概念:笛卡尔积
笛卡尔积(Cartesian product)是数学中两个集合的乘积,表与表的交叉连接本质就是计算笛卡尔积:
- 假设集合 A = {1,2},集合 B = {3,4,5};
- 笛卡尔积 A×B = {(1,3), (1,4), (1,5), (2,3), (2,4), (2,5)};
- 表的笛卡尔积:结果行数 = 表1行数 × 表2行数,字段数 = 表1字段数 + 表2字段数。
注意:笛卡尔积不满足交换律(A×B ≠ B×A),且包含大量无业务意义的数据(如“学生A对应所有课程”),需通过 WHERE
子句筛选有效数据。
(2)语法格式
-- 显式交叉连接(官方推荐) SELECT <字段名> FROM <表1> CROSS JOIN <表2> [WHERE 子句]; -- 隐式交叉连接(逗号分隔表名) SELECT <字段名> FROM <表1>, <表2> [WHERE 子句];
- 字段名:可指定表别名(如
表1.字段1 AS 别名
),避免字段名重复; - WHERE 子句:可选,用于筛选笛卡尔积中的有效数据(无此子句则返回完整笛卡尔积)。
(3)实战案例
以“学生信息表(tb_students_info)”和“课程表(tb_course)”为例,演示交叉连接的使用:
步骤1:创建并查看两张表的原始数据:
mysql> create database xuexiao; Query OK, 1 row affected (0.00 sec) mysql> use xuexiao; Database changed mysql> show tables; Empty set (0.00 sec) mysql> CREATE TABLE IF NOT EXISTS tb_course ( -> id INT PRIMARY KEY AUTO_INCREMENT, -> course_name VARCHAR(50) NOT NULL -> ); Query OK, 0 rows affected (0.00 sec) mysql> CREATE TABLE IF NOT EXISTS tb_students_info( -> id INT PRIMARY KEY AUTO_INCREMENT, -> name VARCHAR(50) NOT NULL, -> age INT, -> sex VARCHAR(10), -> height INT, -> course_id INT, -> FOREIGN KEY (course_id) REFERENCES tb_course(id) -> ); Query OK, 0 rows affected (0.00 sec) mysql> INSERT INTO tb_course (course_name) VALUES -> ('Java'),('MySQL'),('Python'),('Go'),('C++'); Query OK, 5 rows affected (0.00 sec) Records: 5 Duplicates: 0 Warnings: 0 mysql> ALTER TABLE tb_students_info -> MODIFY COLUMN sex VARCHAR(10) -> CHARACTER SET utf8mb4 -> COLLATE utf8mb4_unicode_ci; Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> INSERT INTO tb_students_info (name, age, sex, height, course_id) VALUES -> ('Dany', 25, '男', 160, 1), -> ('Green', 23, '男', 158, 2), -> ('Henry', 23, '女', 185, 1), -> ('Jane', 22, '男', 162, 3), -> ('Jim', 24, '女', 175, 2), -> ('John', 21, '女', 172, 4), -> ('Lily', 22, '男', 165, 4), -> ('Susan', 23, '男', 170, 5), -> ('Thomas', 22, '女', 178, 5), -> ('Tom', 23, '女', 165, 5); Query OK, 10 rows affected (0.00 sec) Records: 10 Duplicates: 0 Warnings: 0 mysql> ALTER TABLE tb_students_info -> MODIFY COLUMN sex VARCHAR(10) -> CHARACTER SET utf8mb4 -> COLLATE utf8mb4_unicode_ci; Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0
-- 查看学生信息表 mysql> SELECT * FROM tb_students_info; +----+--------+------+------+--------+-----------+ | id | name | age | sex | height | course_id | -- course_id:关联课程表的外键 +----+--------+------+------+--------+-----------+ | 1 | Dany | 25 | 男 | 160 | 1 | | 2 | Green | 23 | 男 | 158 | 2 | | 3 | Henry | 23 | 女 | 185 | 1 | | 4 | Jane | 22 | 男 | 162 | 3 | | 5 | Jim | 24 | 女 | 175 | 2 | | 6 | John | 21 | 女 | 172 | 4 | | 7 | Lily | 22 | 男 | 165 | 4 | | 8 | Susan | 23 | 男 | 170 | 5 | | 9 | Thomas | 22 | 女 | 178 | 5 | | 10 | Tom | 23 | 女 | 165 | 5 | +----+--------+------+------+--------+-----------+ 10 rows in set (0.00 sec) -- 查看课程表 mysql> SELECT * FROM tb_course; +----+-------------+ | id | course_name | -- id:主键,与学生表的 course_id 关联 +----+-------------+ | 1 | Java | | 2 | MySQL | | 3 | Python | | 4 | Go | | 5 | C++ | +----+-------------+ 5 rows in set (0.00 sec)
步骤2:查询完整笛卡尔积(无 WHERE 子句)
mysql> SELECT * FROM tb_course CROSS JOIN tb_students_info; +----+-------------+----+--------+------+------+--------+-----------+ | id | course_name | id | name | age | sex | height | course_id | +----+-------------+----+--------+------+------+--------+-----------+ | 1 | Java | 1 | Dany | 25 | 男 | 160 | 1 | | 2 | MySQL | 1 | Dany | 25 | 男 | 160 | 1 | | 3 | Python | 1 | Dany | 25 | 男 | 160 | 1 | | 4 | Go | 1 | Dany | 25 | 男 | 160 | 1 | | 5 | C++ | 1 | Dany | 25 | 男 | 160 | 1 | *****省略中间 40 行****** | 5 | C++ | 10 | Tom | 23 | 女 | 165 | 5 | +----+-------------+----+--------+------+------+--------+-----------+ 50 rows in set (0.00 sec)
结果分析:返回 10×5=50 行数据,包含大量无效关联(如“Dany 对应所有课程”),实际业务中几乎不用此方式。
步骤3:带 WHERE 子句的交叉连接(筛选有效数据)
通过 WHERE
子句匹配“课程表 id”与“学生表 course_id”,消除无效数据:
mysql> SELECT * FROM tb_course CROSS JOIN tb_students_info -> WHERE tb_students_info.course_id = tb_course.id; +----+-------------+----+--------+------+------+--------+-----------+ | id | course_name | id | name | age | sex | height | course_id | +----+-------------+----+--------+------+------+--------+-----------+ | 1 | Java | 1 | Dany | 25 | 男 | 160 | 1 | | 1 | Java | 3 | Henry | 23 | 女 | 185 | 1 | | 2 | MySQL | 2 | Green | 23 | 男 | 158 | 2 | | 2 | MySQL | 5 | Jim | 24 | 女 | 175 | 2 | | 3 | Python | 4 | Jane | 22 | 男 | 162 | 3 | | 4 | Go | 6 | John | 21 | 女 | 172 | 4 | | 4 | Go | 7 | Lily | 22 | 男 | 165 | 4 | | 5 | C++ | 8 | Susan | 23 | 男 | 170 | 5 | | 5 | C++ | 9 | Thomas | 22 | 女 | 178 | 5 | | 5 | C++ | 10 | Tom | 23 | 女 | 165 | 5 | +----+-------------+----+--------+------+------+--------+-----------+ 10 rows in set (0.00 sec)
结果分析:仅返回 10 行有效数据,与学生表行数一致,实现“学生-课程”的正确关联。
(4)注意事项
- 交叉连接效率低:MySQL 会先生成完整笛卡尔积,再筛选有效数据,表数据量大时(如各1000行)会产生 100万行临时数据,严重影响性能;
- 替代方案:实际多表查询优先使用“内连接”或“外连接”,效率更高且语法更清晰。
2. 内连接(INNER JOIN)——筛选匹配数据
内连接是最常用的多表查询方式,通过 INNER JOIN
关键字和 ON
子句设置关联条件,仅返回两张表中满足关联条件的记录,自动消除无效数据,无需手动筛选笛卡尔积。
(1)核心逻辑
内连接的本质是“交集查询”:仅保留表1和表2中“关联字段值相等”的记录,不满足条件的记录(如“无对应课程的学生”“无学生的课程”)会被过滤。
(2)语法格式
SELECT <字段名> FROM <表1> INNER JOIN <表2> ON <关联条件>; -- 简化写法:INNER 可省略,直接用 JOIN SELECT <字段名> FROM <表1> JOIN <表2> ON <关联条件>;
ON 子句
:必选,用于定义表间关联条件(如表1.外键 = 表2.主键
),优先级高于WHERE
子句;- 多表内连接:连续使用
JOIN
即可,如表1 JOIN 表2 ON 条件1 JOIN 表3 ON 条件2
。
(3)实战案例:查询学生姓名及对应课程名称
需求:从 tb_students_info
和 tb_course
中,查询学生姓名(name)及对应课程名称(course_name),关联条件为“学生表 course_id = 课程表 id”。
-- 给表设置别名(s 代表学生表,c 代表课程表),简化语法 mysql> SELECT s.name, c.course_name -> FROM tb_students_info s -> INNER JOIN tb_course c -> ON s.course_id = c.id; +--------+-------------+ | name | course_name | +--------+-------------+ | Dany | Java | | Henry | Java | | Green | MySQL | | Jim | MySQL | | Jane | Python | | John | Go | | Lily | Go | | Susan | C++ | | Thomas | C++ | | Tom | C++ | +--------+-------------+ 10 rows in set (0.00 sec)
(4)关键说明
- 表别名:当表名较长时,用
表名 别名
简化写法(如tb_students_info s
),后续通过“别名.字段”引用字段; - 字段唯一性:若两张表有同名字段(如
id
),需显式指定表名或别名(如s.id
或c.id
),避免歧义; - 效率优势:内连接直接按关联条件匹配数据,无需生成完整笛卡尔积,效率远高于交叉连接。
3. 外连接(LEFT/RIGHT JOIN)——保留部分表的全部数据
内连接仅返回“双方匹配”的记录,而外连接会以一张表为“基表”,保留基表的全部记录,另一张表(参考表)匹配不到的记录用 NULL
填充,适用于“需保留所有基表数据”的场景(如“查询所有学生,包括无课程的学生”)。
外连接分为 左外连接(LEFT JOIN) 和 右外连接(RIGHT JOIN),核心区别是“基表的选择”。
(1)左外连接(LEFT OUTER JOIN)
- 定义:以
LEFT JOIN
左侧的表为基表,保留基表的全部记录,右侧表(参考表)匹配到则显示对应数据,匹配不到则字段值为NULL
; - 语法:
SELECT 字段 FROM 基表 LEFT OUTER JOIN 参考表 ON 关联条件;
(OUTER
可省略,简化为LEFT JOIN
); - 核心场景:需保留左侧表的所有数据(如“查询所有学生,包括无课程的学生”)。
实战案例:查询所有学生及对应课程(含无课程的学生)
步骤1:先添加“无课程的学生”(course_id=7,课程表无 id=7 的课程):
-- 临时禁用外键约束 ysql> SET FOREIGN_KEY_CHECKS=0; Query OK, 0 rows affected (0.01 sec) mysql> INSERT INTO tb_students_info (name, age, sex, height, course_id) -> VALUES ('LiMing', 22, '男', 180, 7); Query OK, 1 row affected (0.01 sec) -- 恢复外键约束 mysql> SET FOREIGN_KEY_CHECKS=1; Query OK, 0 rows affected (0.00 sec)
步骤2:执行左外连接查询:
mysql> SELECT s.name, c.course_name -> FROM tb_students_info s -> LEFT JOIN tb_course c -> ON s.course_id = c.id; +--------+-------------+ | name | course_name | +--------+-------------+ | Dany | Java | | Henry | Java | | Green | MySQL | | Jim | MySQL | | Jane | Python | | John | Go | | Lily | Go | | Susan | C++ | | Thomas | C++ | | Tom | C++ | | LiMing | C++ | | LiMing | NULL | # 无对应课程,course_name 为null +--------+-------------+ 12 rows in set (0.00 sec)
结果分析:基表(学生表)的 11 条记录全部保留,LiMing 因无对应课程,课程名称显示 NULL
。
(2)右外连接(RIGHT OUTER JOIN)
- 定义:以
RIGHT JOIN
右侧的表为基表,保留基表的全部记录,左侧表(参考表)匹配不到则字段值为NULL
; - 语法:
SELECT 字段 FROM 参考表 RIGHT OUTER JOIN 基表 ON 关联条件;
(OUTER
可省略,简化为RIGHT JOIN
); - 核心场景:需保留右侧表的所有数据(如“查询所有课程,包括无学生的课程”)。
实战案例:查询所有课程及对应学生(含无学生的课程)
步骤1:先添加“无学生的课程”(id=6,学生表无 course_id=6 的记录):
mysql> INSERT INTO tb_course (id, course_name) -> VALUES (6, 'HTML'); Query OK, 1 row affected (0.00 sec)
步骤2:执行右外连接查询:
mysql> SELECT s.name, c.course_name -> FROM tb_students_info s -> RIGHT JOIN tb_course c -> ON s.course_id = c.id; +--------+-------------+ | name | course_name | +--------+-------------+ | Dany | Java | | Henry | Java | | Green | MySQL | | Jim | MySQL | | Jane | Python | | John | Go | | Lily | Go | | Susan | C++ | | Thomas | C++ | | Tom | C++ | | LiMing | C++ | | NULL | HTML | +--------+-------------+ 12 rows in set (0.01 sec)
结果分析:基表(课程表)的 6 条记录全部保留,HTML 课程因无学生,学生姓名显示 NULL
。
(3)外连接的关键区别
连接类型 | 基表 | 保留数据 | 参考表匹配不到时 | 适用场景 |
---|---|---|---|---|
左外连接 | LEFT 左侧表 | 左侧表全部记录 | 右侧表字段为 NULL | 保留左侧表所有数据 |
右外连接 | RIGHT 右侧表 | 右侧表全部记录 | 左侧表字段为 NULL | 保留右侧表所有数据 |
4. 分组查询(GROUP BY)——按字段分组统计
分组查询通过 GROUP BY
关键字,按一个或多个字段对查询结果分组,常与 聚合函数(如 COUNT()
、SUM()
)结合,实现数据统计(如“按性别统计学生人数”)。
(1)核心语法
SELECT <分组字段>, <聚合函数> FROM <表名> [JOIN <关联表> ON <条件>] [WHERE <筛选条件>] GROUP BY <分组字段> [WITH ROLLUP];
- 聚合函数:
COUNT()
(统计行数)、SUM()
(求和)、AVG()
(平均值)、MAX()
(最大值)、MIN()
(最小值); WITH ROLLUP
:可选,在所有分组后追加一行“总计”记录;- 执行顺序:
WHERE
(筛选行)→GROUP BY
(分组)→ 聚合函数(统计)。
(2)实战案例
案例1:按性别分组,统计学生人数
mysql> SELECT sex, COUNT(*) AS student_count -> FROM tb_students_info -> GROUP BY sex; +------+---------------+ | sex | student_count | +------+---------------+ | 女 | 5 | | 男 | 6 | +------+---------------+ 2 rows in set (0.00 sec)
案例2:按性别分组,显示每组学生姓名(GROUP_CONCAT())
GROUP_CONCAT()
函数可将分组内的指定字段值拼接为字符串:
mysql> SELECT sex, GROUP_CONCAT(name) AS student_names -> FROM tb_students_info -> GROUP BY sex; +------+----------------------------------------+ | sex | student_names | +------+----------------------------------------+ | 女 | Henry,Jim,John,Thomas,Tom | | 男 | Dany,Green,Jane,Lily,Susan,LiMing | +------+----------------------------------------+ 2 rows in set (0.00 sec)
案例3:按“年龄+性别”多字段分组
多字段分组时,先按第一个字段分组,第一个字段值相同时再按第二个字段分组:
mysql> SELECT age, sex, GROUP_CONCAT(name) AS student_names -> FROM tb_students_info -> GROUP BY age, sex; +------+------+------------------+ | age | sex | student_names | +------+------+------------------+ | 21 | 女 | John | | 22 | 女 | Thomas | | 22 | 男 | Jane,Lily,LiMing | | 23 | 女 | Henry,Tom | | 23 | 男 | Green,Susan | | 24 | 女 | Jim | | 25 | 男 | Dany | +------+------+------------------+ 7 rows in set (0.00 sec)
案例4:分组后添加总计(WITH ROLLUP)
mysql> SELECT sex, COUNT(*) AS student_count -> FROM tb_students_info -> GROUP BY sex WITH ROLLUP; +------+---------------+ | sex | student_count | +------+---------------+ | 女 | 5 | | 男 | 6 | | NULL | 11 | +------+---------------+ 3 rows in set (0.00 sec)
5. 子查询——嵌套查询实现复杂逻辑
子查询(嵌套查询)是将一个查询语句(子查询)嵌套在另一个查询语句(父查询)中,子查询的结果作为父查询的条件或数据源,适用于“需先获取中间结果”的复杂场景(如“查询学习 Java 课程的学生姓名”)。
(1)核心语法
子查询常位于 WHERE
子句中,格式如下:
SELECT <父查询字段> FROM <父查询表> WHERE <表达式> <操作符> (子查询);
- 操作符:支持
IN
/NOT IN
(匹配子查询结果集中的值)、EXISTS
/NOT EXISTS
(判断子查询结果集是否为空)、=
/<>
(等于/不等于子查询结果,子查询需返回单行单列)。
(2)实战案例
案例1:查询学习 Java 课程的学生姓名(IN 关键字)
需求:先查询“Java 课程的 id”,再根据 id 查询对应学生姓名。
-- 子查询:获取 Java 课程的 id mysql> SELECT id FROM tb_course WHERE course_name = 'Java'; +----+ | id | +----+ | 1 | +----+ 1 row in set (0.00 sec) -- 父查询:根据 id=1 查询学生姓名 mysql> SELECT name -> FROM tb_students_info -> WHERE course_id IN (SELECT id FROM tb_course WHERE course_name = 'Java'); +-------+ | name | +-------+ | Dany | | Henry | +-------+ 2 rows in set (0.01 sec)
案例2:查询没有学习 Java 课程的学生姓名(NOT IN 关键字)
mysql> SELECT name -> FROM tb_students_info -> WHERE course_id NOT IN (SELECT id FROM tb_course WHERE course_name = 'Java'); +--------+ | name | +--------+ | Green | | Jane | | Jim | | John | | Lily | | Susan | | Thomas | | Tom | | LiMing | +--------+ 9 rows in set (0.00 sec)
案例3:判断课程是否存在,再查询学生(EXISTS 关键字)
EXISTS
仅判断子查询结果集是否为空(不为空则返回 TRUE
,为空则返回 FALSE
),不关心具体值:
-- 若存在 id=1 的课程(Java),则查询所有学生 mysql> SELECT * -> FROM tb_students_info -> WHERE EXISTS (SELECT course_name FROM tb_course WHERE id=1); +----+--------+------+------+--------+-----------+ | id | name | age | sex | height | course_id | +----+--------+------+------+--------+-----------+ | 1 | Dany | 25 | 男 | 160 | 1 | | 2 | Green | 23 | 男 | 158 | 2 | | 3 | Henry | 23 | 女 | 185 | 1 | | 4 | Jane | 22 | 男 | 162 | 3 | | 5 | Jim | 24 | 女 | 175 | 2 | | 6 | John | 21 | 女 | 172 | 4 | | 7 | Lily | 22 | 男 | 165 | 4 | | 8 | Susan | 23 | 男 | 170 | 5 | | 9 | Thomas | 22 | 女 | 178 | 5 | | 10 | Tom | 23 | 女 | 165 | 5 | | 15 | LiMing | 22 | 男 | 180 | 7 | +----+--------+------+------+--------+-----------+ 11 rows in set (0.00 sec)
案例4:多条件结合 EXISTS(查询年龄>24 的学生)
mysql> SELECT * -> FROM tb_students_info -> WHERE age>24 AND EXISTS (SELECT course_name FROM tb_course WHERE id=1); +----+------+------+------+--------+-----------+ | id | name | age | sex | height | course_id | +----+------+------+------+--------+-----------+ | 1 | Dany | 25 | 男 | 160 | 1 | +----+------+------+------+--------+-----------+ 1 row in set (0.01 sec)
(3)子查询 vs 表连接
- 子查询:语法更直观,适合“先获取中间结果”的场景(如先查课程 id 再查学生),但多层嵌套时效率可能较低;
- 表连接:效率更高,适合“直接关联表获取数据”的场景(如学生表 join 课程表),但复杂逻辑时可读性较差;
- 选择原则:简单关联用表连接,复杂中间逻辑用子查询。
到此这篇关于MySQL 多表联合查询与数据备份恢复全攻略的文章就介绍到这了,更多相关mysql多表联合查询内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!