6.1 本章目标
- 了解联合查询的计算过程。
- 掌握内连接、左外连接、右外连接和自连接。
- 掌握子查询和合并查询。
- 掌握插入查询结果以及根据查询结果创建表。
6.2 联合查询简介
6.2.1 为什么需要联合查询
在数据库设计过程中,由于数据库范式的要求,数据通常被拆分到多个表中。
例如:
student:学生表,保存学生基本信息。class:班级表,保存班级信息。course:课程表,保存课程信息。score:成绩表,保存学生的课程成绩。
如果需要查询学生的姓名、班级、课程和成绩,就需要从多张表中获取数据,并根据表之间的关联关系进行组合。
联合查询的核心:将多个表组合起来,从多个表中获取所需要的数据。
6.2.2 多表联合查询的计算过程
多表查询可以按照以下过程理解:
- 确定参与查询的所有数据表。
- 对参与查询的表进行组合,形成笛卡尔积。
- 根据表之间的关联关系,过滤不符合条件的记录。
- 根据其他查询条件进一步筛选数据。
- 根据需要选择字段、分组和排序。
1. 笛卡尔积
笛卡尔积是指两个集合中的每个元素分别与另一个集合中的每个元素组合。
假设:
- 学生表有 8 条记录。
- 班级表有 3 条记录。
那么两个表的笛卡尔积共有:
8*3=24
条记录。
sql
-- 查询 student 表和 class 表的笛卡尔积
SELECT *
FROM student, class;
-- 查询 N 张表的时候就可以把 N 张表都放在后面逗号隔开
此时,每个学生都会与班级表中的每个班级组合,并不一定符合实际的班级归属关系。
2. 根据关联条件过滤数据
学生表中的
class_id对应班级表中的id。
sql
-- 根据学生所属班级筛选匹配的记录
SELECT *
FROM student, class
WHERE student.class_id = class.id;
加入连接条件后,只保留学生所属班级与班级表对应的记录。
重点:多表查询需要明确表与表之间的关联条件,否则可能产生大量无意义的组合记录。
3. 多表查询的基本语法
sql
-- 使用逗号连接多个表
SELECT 字段名
FROM 表1, 表2
WHERE 连接条件
AND 其他查询条件;
例如:
sql
-- 查询宋江的个人信息和班级信息
SELECT
s.id,
s.name,
s.sno,
s.age,
c.name AS class_name
FROM student s, class c
WHERE s.class_id = c.id
AND s.name = '宋江';
6.2.3 字段歧义与表名别名
1. 字段歧义
当参与查询的多个表存在同名字段时,直接使用字段名可能产生歧义。
sql
-- 错误示例:student 和 class 都存在 name 字段
SELECT *
FROM student, class
WHERE student.class_id = class.id
AND name = '宋江';--这里的name可能会出错
可能出现错误:
sql
ERROR 1052 (23000): Column 'name' in where clause is ambiguous
因为 MySQL 无法确定
name指的是哪张表中的字段。
正确写法:
sql
-- 使用表名明确指定查询字段
SELECT *
FROM student, class
WHERE student.class_id = class.id
AND student.name = '宋江';
重点:多表查询时,同名字段应使用
**表名.字段名**的形式明确指定。
2. 表名别名
- 为表指定别名,可以简化 SQL 语句。
sql
-- s 是 student 表的别名
-- c 是 class 表的别名
SELECT
s.id,
s.name,
s.sno,
s.age,
s.gender,
s.enroll_date,
c.name AS class_name
FROM student s, class c
WHERE s.class_id = c.id
AND s.name = '宋江';
说明:
student s:将student表的别名设置为s。class c:将class表的别名设置为c。s.name:表示student表中的name字段。c.name:表示class表中的name字段。
注意:定义表别名后,应使用别名引用该表,避免混用造成错误。
6.3 内连接
6.3.1 内连接的概念
内连接(
INNER JOIN)是根据连接条件,将多个表中能够匹配的记录组合起来。只有满足连接条件的记录才会出现在查询结果中。
6.3.2 内连接的语法
1. 使用 WHERE 实现连接
sql
-- 使用 WHERE 指定表之间的关联条件
SELECT 字段名
FROM 表1, 表2
WHERE 连接条件
AND 其他查询条件;
2. 使用 JOIN 实现连接
sql
-- INNER 可以省略
SELECT 字段名
FROM 表1
[INNER] JOIN 表2
ON 连接条件
WHERE 其他查询条件;
INNER JOIN中的INNER可以省略,直接写JOIN。
6.3.3 查询唐三藏的成绩
sql
-- 查询唐三藏的姓名及其所有课程成绩
SELECT
student.name,
score.score
FROM student
JOIN score
ON score.student_id = student.id
WHERE student.name = '唐三藏';
执行过程:
- 确定参与查询的表:
student和score。 - 根据
sc.student_id = s.id关联学生和成绩。 - 使用
WHERE筛选姓名为唐三藏的记录。 - 返回学生姓名和成绩。
6.3.4 查询所有有成绩学生的总成绩
sql
-- 查询每位学生的总成绩
SELECT
s.name,
SUM(sc.score) AS total_score
FROM student AS s
JOIN score AS sc
ON sc.student_id = s.id
GROUP BY s.id, s.name;
课件案例中的部分结果如下:
| 姓名 | 总成绩 |
|---|---|
| 唐三藏 | 300 |
| 孙悟空 | 119.5 |
| 猪悟能 | 200 |
| 沙悟净 | 218 |
| 宋江 | 118 |
| 武松 | 178 |
| 李逵(课件原名为李逹) | 172 |
说明:
JOIN:关联学生表和成绩表。SUM():计算成绩总和。GROUP BY:按照学生分组。s.id是学生的唯一标识,可以避免仅按照姓名分组时不同学生被合并。
6.3.5 查询学生的个人信息、班级、课程及成绩
sql
-- 关联学生表、班级表、成绩表和课程表
SELECT
s.id AS id,
s.name AS 姓名,
s.sno AS 学号,
s.gender AS 性别,
c.name AS 班级,
co.name AS 课程,
sc.score AS 分数
FROM student AS s
JOIN class AS c
ON s.class_id = c.id
JOIN score AS sc
ON s.id = sc.student_id
JOIN course AS co
ON sc.course_id = co.id
ORDER BY s.id;
各表的连接关系如下:
| 关联表 | 连接条件 | 作用 |
|---|---|---|
student、class |
s.class_id = c.id |
查询学生所属班级 |
student、score |
s.id = sc.student_id |
查询学生成绩 |
score、course |
sc.course_id = co.id |
查询成绩对应的课程 |
重点:多表连接时,每增加一张表,都需要明确其与已有表之间的关联关系。
6.4 外连接
6.4.1 外连接的概念
外连接分为左外连接、右外连接和全外连接。
MySQL 不支持直接使用标准的
FULL OUTER JOIN。
| 连接类型 | 返回内容 | 没有匹配记录时 |
|---|---|---|
左外连接 LEFT JOIN |
左表全部记录及右表匹配记录 | 右表字段为 NULL |
右外连接 RIGHT JOIN |
右表全部记录及左表匹配记录 | 左表字段为 NULL |
| 全外连接 | 左右表全部记录 | 未匹配的一侧为 NULL |
6.4.2 左外连接
1. 基本语法
sql
-- 左外连接:左表的所有记录都会保留
SELECT 字段名
FROM 表1
LEFT JOIN 表2
ON 连接条件;
左外连接的核心:左表全部保留,右表只返回匹配记录。
如果右表中没有匹配记录,则右表对应字段显示为NULL。
2. 查询所有学生及其成绩
sql
-- 保留全部学生,包括没有成绩记录的学生
SELECT
s.id,
s.name,
s.sno,
s.age,
sc.* --成绩表的所有列
FROM student AS s
LEFT JOIN score AS sc
ON sc.student_id = s.id;
如果某个学生没有参加考试,则该学生仍然会出现在结果中,成绩表对应的字段显示为 NULL。
3. 查询没有参加考试的学生
sql
-- 左连接后,筛选成绩表中没有匹配记录的学生
SELECT s.*
FROM student s
LEFT JOIN score sc
ON sc.student_id = s.id
WHERE sc.score IS NULL;
课件案例中,查询结果为:
| id | name | sno | class_id |
|---|---|---|---|
| 8 | 想毕业 | 200004 | 2 |
执行过程:
- 以学生表为左表。
- 连接成绩表。
- 保留所有学生。
- 筛选成绩表中没有匹配记录的数据。
这里的
sc.score IS NULL在课件案例中能够筛选出没有成绩的学生。如果成绩字段允许存在真实的NULL值,建议使用关联字段判断:
sql
-- 使用右表的关联字段判断是否存在匹配记录
SELECT s.*
FROM student s
LEFT JOIN score sc
ON sc.student_id = s.id
WHERE sc.student_id IS NULL;
重点: **LEFT JOIN** 配合 **IS NULL**,可以查询左表中没有匹配右表记录的数据。
6.4.3 右外连接
1. 基本语法
sql
-- 右外连接:右表的所有记录都会保留
SELECT 字段名
FROM 表1
RIGHT JOIN 表2
ON 连接条件;
右外连接的核心:右表全部保留,左表只返回匹配记录。
如果左表没有匹配记录,则左表对应字段显示为NULL。
2. 查询所有班级及其学生
-- 保留全部班级,即使某个班级没有学生
SELECT *
FROM student s
RIGHT JOIN class c
ON c.id = s.class_id;
案例中的班级表包含:
- Java001班
- C++001班
- 前端001班 其中,前端001班没有对应的学生记录,但使用右外连接后仍然会显示在查询结果中,学生表的字段为
NULL。
3. 查询没有学生的班级
sql
-- 右连接后,筛选学生表中没有匹配记录的班级
SELECT c.*
FROM student s
RIGHT JOIN class c
ON c.id = s.class_id
WHERE s.id IS NULL;
课件案例中的查询结果:
| id | name |
|---|---|
| 3 | 前端001班 |
6.4.4 左外连接与右外连接的区别
| 对比项目 | LEFT JOIN | RIGHT JOIN |
|---|---|---|
| 完整保留的表 | 左表 | 右表 |
| 未匹配记录 | 右表字段为 NULL |
左表字段为 NULL |
| 查询没有成绩的学生 | 适用 | 不常用 |
| 查询没有学生的班级 | 不常用 | 适用 |
记忆方法:LEFT 保左表,RIGHT 保右表。
6.4.5 全外连接
全外连接同时保留左右两张表的全部记录。
- 能够匹配的记录进行组合。
- 左表没有匹配的记录,右表字段为
NULL。 - 右表没有匹配的记录,左表字段为
NULL。
MySQL 不支持直接使用FULL OUTER JOIN。
可以使用左连接和右连接结合UNION的方式实现类似效果。
sql
-- 第一部分:保留左表全部记录
SELECT s.id AS student_id, s.name AS student_name,
c.id AS class_id, c.name AS class_name
FROM student s
LEFT JOIN class c
ON s.class_id = c.id
UNION
-- 第二部分:保留右表全部记录
SELECT s.id AS student_id, s.name AS student_name,
c.id AS class_id, c.name AS class_name
FROM student s
RIGHT JOIN class c
ON s.class_id = c.id;
这里使用 UNION 合并两个结果集并去重。
6.5 自连接
6.5.1 自连接的概念
自连接是将同一张表当作两张不同的表进行连接查询。
其本质仍然是表连接,只不过参与连接的是同一张物理表。
自连接可以用于:
- 比较同一张表中不同记录的数据。
- 实现行与行之间的比较。
- 查询具有上下级关系的数据。
- 比较同一学生不同课程的成绩。
重点:自连接需要为同一张表指定不同的别名。
6.5.2 自连接的基本语法
sql
-- 将同一张表分别命名为 s1 和 s2
SELECT *
FROM 表名 s1, 表名 s2
WHERE 连接条件;
错误示例:
sql
-- 错误:同一张表重复出现,但没有使用不同别名
SELECT *
FROM score, score;
可能出现错误:
sql
ERROR 1066 (42000): Not unique table/alias: 'score'
正确写法:
sql
-- 为同一张表设置不同别名
SELECT *
FROM score AS s1, score AS s2;
6.5.3 查询 MySQL 成绩高于 Java 成绩的记录
1. 查询课程编号
sql
-- 查询 Java 和 MySQL 对应的课程 id
SELECT *
FROM course
WHERE name = 'Java'
OR name = 'MySQL';
案例中:
- Java 的课程 id 为
1。 - MySQL 的课程 id 为
3。
2. 使用自连接比较成绩
sql
-- s1 表示 MySQL 成绩,s2 表示 Java 成绩
-- 先匹配同一学生,再比较两门课程的分数
SELECT s1.*
FROM score s1, score s2
WHERE s1.student_id = s2.student_id
AND s1.course_id = 3
AND s2.course_id = 1
AND s1.score > s2.score;
课件案例中的结果:
| id | score | student_id | course_id |
|---|---|---|---|
| 2 | 98.5 | 1 | 3 |
| 8 | 68 | 3 | 3 |
3. 连接条件分析
| 条件 | 作用 |
|---|---|
s1.student_id = s2.student_id |
确保比较的是同一学生 |
s1.course_id = 3 |
指定 s1 为 MySQL 成绩 |
s2.course_id = 1 |
指定 s2 为 Java 成绩 |
s1.score > s2.score |
判断 MySQL 成绩是否更高 |
6.5.4 使用课程名称进行自连接
如果不希望直接写死课程编号,可以继续连接课程表。
sql
-- 根据课程名称识别两条成绩记录对应的课程
SELECT s1.*
FROM score s1, score s2, course c1, course c2
WHERE s1.student_id = s2.student_id
AND s1.course_id = c1.id
AND s2.course_id = c2.id
AND s1.score > s2.score
AND c1.name = 'MySQL'
AND c2.name = 'Java';
执行逻辑:
s1和s2分别表示同一学生的两条成绩记录。- 根据学生编号匹配同一学生。
- 连接课程表,识别两条成绩分别属于哪门课程。
- 筛选 MySQL 成绩高于 Java 成绩的记录。
6.5.5 查询学生、班级和两门课程的成绩
sql
-- 查询 MySQL 成绩高于 Java 成绩的学生信息
SELECT
stu.name AS 姓名,
c.name AS 班级,
s1.score AS MySQL分数,
s2.score AS Java分数
FROM score AS s1,
score AS s2,
course AS c1,
course AS c2,
student AS stu,
class AS c
WHERE s1.student_id = s2.student_id
AND s1.course_id = c1.id
AND s2.course_id = c2.id
AND s1.score > s2.score
AND stu.id = s1.student_id
AND stu.class_id = c.id
AND c1.name = 'MySQL'
AND c2.name = 'Java';
课件案例中的结果:
| 姓名 | 班级 | MySQL分数 | Java分数 |
|---|---|---|---|
| 唐三藏 | Java001班 | 98.5 | 70.5 |
| 猪悟能 | Java001班 | 68 | 33 |
自连接解题思路:确定比较对象 → 为同一张表设置不同别名 → 关联同一主体 → 筛选比较条件。
6.6 子查询 (一般不用)
6.6.1 子查询的概念
子查询是将一个
SELECT语句的查询结果作为另一个SELECT语句的条件,也称为嵌套查询。
一般情况下:
- 内层查询负责获取条件值或结果集。
- 外层查询根据内层查询结果筛选数据。
6.6.2 子查询的基本语法
sql
-- 子查询的基本结构
SELECT *
FROM table1
WHERE col_name1 = (
SELECT col_name1
FROM table2
WHERE col_name2 = 查询条件
);
子查询也可以嵌套多层,或者使用 IN 等运算符处理多行结果。
6.6.3 单行子查询
单行子查询是指内层查询返回一行数据。
当内层查询返回一个标量值时,可以使用=、>、<等比较运算符。
示例:查询与"不想毕业"同班的学生
- 一般查询语句
sql
-- 先查询"不想毕业"所在班级的 class_id
SELECT class_id
FROM student
WHERE name = '不想毕业';
--输出:1
-- 知道班级 id = 1,再查询该班级的所有学生
SELECT *
FROM student
WHERE class_id = 1;
- 子查询语句
sql
-- 内层查询:获取"不想毕业"所在班级的 id
-- 外层查询:查询该班级的所有学生
SELECT *
FROM student
WHERE class_id = (
SELECT class_id
FROM student
WHERE name = '不想毕业'
);
执行过程:
- 内层查询获取"不想毕业"的班级编号。
- 外层查询查找班级编号相同的学生。
- 返回同班学生的完整信息。
重点:使用 **=** 时,子查询应返回一个值。如果返回多行,通常会发生错误。
6.6.4 多行子查询
多行子查询是指内层查询返回多行数据。
课件重点使用的运算符:
IN:判断某个值是否在结果集中。NOT IN:判断某个值是否不在结果集中。
1. 查询 Java 或 MySQL 课程的成绩
- 一般查询语句
sql
-- 查询 Java 和 MySQL 的课程 id
SELECT id
FROM course
WHERE name IN ('Java', 'MySQL');
-- 查询这些课程对应的成绩
SELECT *
FROM score
WHERE course_id IN (1, 2);
- 子查询语句
sql
-- 内层查询获取 Java 和 MySQL 的课程 id
-- 外层查询筛选这些课程对应的成绩
SELECT *
FROM score
WHERE course_id IN (
SELECT id
FROM course
WHERE name = 'Java'
OR name = 'MySQL'
);
执行过程:
- 内层查询获取 Java 和 MySQL 的课程编号。
- 外层查询判断
course_id是否在结果集中。 - 返回符合条件的成绩记录。
6.7 UNION合并查询
- 记忆口诀:列数要相同,类型要兼容,按位置合并;UNION 去重,UNION ALL 不去重。
UNION用于合并多个查询结果,将查出来的临时表单组合到一起去,并自动去除重复行。
- 基本语法:
sql
-- 合并两个查询结果,并自动去重
SELECT 字段1, 字段2 FROM 表1 WHERE 查询条件
UNION
SELECT 字段1, 字段2FROM 表2 WHERE 查询条件;
示例:查询 student 中 id 小于 3 的学生及 student1 中的所有学生
sql
-- 合并两个查询结果,并自动去除完全重复的记录
SELECT * FROM student WHERE id < 3
UNION
SELECT * FROM student1;
- SELECT * FROM student WHERE id < 3; 查询临时表单
| id | name |
|---|---|
| 1 | 唐三藏 |
| 2 | 猪八戒 |
SELECT * FROM student1;查询临时表单
| id | name |
|---|---|
| 1 | 唐三藏 |
| 2 | 沙僧 |
| 3 | 猪八戒 |
| 4 | 猴哥 |
两个查询结果中都存在唐三藏,且对应查询列的值完全相同,因此最终结果中唐三藏只保留一条记录。
| id | name |
|---|---|
| 1 | 唐三藏 |
重点:
**UNION**会根据整行查询结果判断重复,而不是只根据某一个字段判断。
6.7.1 UNION ALL
UNION ALL用于合并多个查询结果,但不会去除重复行。
- 基本语法:
sql
-- 合并两个查询结果,保留全部重复记录
SELECT 字段1, 字段2 FROM 表1 WHERE 查询条件
UNION ALL
SELECT 字段1, 字段2 FROM 表2 WHERE 查询条件;
示例:合并两个学生查询结果
sql
-- 合并两个查询结果,但不进行去重
SELECT * FROM student WHERE id < 3
UNION ALL
SELECT * FROM student1;
由于
student和student1中均存在相同的唐三藏记录,因此结果中会保留两条唐三藏记录。
| id | name |
|---|---|
| 1 | 唐三藏 |
| 1 | 唐三藏 |
6.7.2 UNION 与 UNION ALL 的标准对比示例
假设有两张表
student_a和student_b。
student_a 表:
| id | name |
|---|---|
| 1 | 萧炎 |
| 2 | 药尘 |
student_b 表:
| id | name |
|---|---|
| 2 | 药尘 |
| 3 | 美杜莎 |
1. 使用 UNION
sql
-- 合并结果并去除重复行
SELECT id, name FROM student_a
UNION
SELECT id, name FROM student_b;
结果:
| id | name |
|---|---|
| 1 | 萧炎 |
| 2 | 药尘 |
| 3 | 美杜莎 |
两张表中完全相同的
(2, '药尘')只保留一条。
2. 使用 UNION ALL
sql
-- 合并结果但保留重复行
SELECT id, name FROM student_a
UNION ALL
SELECT id, name FROM student_b;
结果包含四行:
| id | name |
|---|---|
| 1 | 萧炎 |
| 2 | 药尘 |
| 2 | 药尘 |
| 3 | 美杜莎 |
两张表中重复的 (2, '药尘') 被保留两次。
6.7.4 合并查询的语法规则
1. 每个 SELECT 的列数必须一致
sql
-- 正确:两个查询均返回两列
SELECT id, name FROM student_a
UNION
SELECT id, name FROM student_b;
错误示例:
sql
-- 错误:第一个查询返回两列,第二个查询返回一列
SELECT id, name FROM student_a
UNION
SELECT name FROM student_b;
2. 对应列的数据类型必须兼容
两张表中进行的相同列的数据类型需一致,以便合并为统一结果集。
sql
-- 对应列的数据类型一致,可以进行合并
SELECT id, name FROM student_a
UNION ALL
SELECT student_id, student_name FROM student_b;
3. 列名可以不同,但列的顺序必须对应
合并查询按照列的位置匹配,而不是根据列名匹配。
例如:
sql
-- 第一列对应第一列,第二列对应第二列
SELECT id, name FROM student_a
UNION ALL
SELECT student_id, student_name FROM student_b;
即使第二个查询中的列名不同,只要列数一致、对应列的数据类型一致,就可以进行合并。
4. 最终结果的列名通常由第一个 SELECT 决定
sql
-- 最终结果集的列名通常为 id 和 name
SELECT id, name FROM student_a
UNION
SELECT student_id, student_name FROM student_b;
第二个查询中的
student_id和student_name不会成为最终结果集的列名。
结果表单:
| id | name |
|---|---|
| 1 | 张三 |
5. UNION 去重,UNION ALL 不去重
sql
-- 自动去重
SELECT id, name FROM student_a
UNION
SELECT id, name FROM student_b;
-- 保留重复记录
SELECT id, name FROM student_a
UNION ALL
SELECT id, name FROM student_b;
6. ORDER BY 通常放在整个合并查询的末尾
sql
-- 对合并后的整个结果集按照 id 升序排列
SELECT id, name FROM student_a
UNION ALL
SELECT id, nameFROM student_b
ORDER BY id ASC;
- 这里的
ORDER BY对最终合并结果进行排序。
7. LIMIT 可以限制最终结果数量
sql
-- 对合并后的结果排序,只返回前 3 条
SELECT id, name FROM student_a
UNION ALL
SELECT id, name FROM student_b
ORDER BY id ASC
LIMIT 3;
- 这里的
LIMIT 3限制的是最终合并结果集的行数。 - 如果需要分别对各个查询分支排序或限制记录数,应通过括号或派生表明确各分支的查询结构。
6.7.5 合并查询规则总结
| 规则 | 具体要求 |
|---|---|
| 列数 | 每个 SELECT 返回的列数必须相同 |
| 数据类型 | 对应位置的列应具有兼容的数据类型 |
| 列名 | 可以不同,最终列名通常由第一个查询决定 |
| 列顺序 | 按列的位置对应,而不是按列名对应 |
| UNION | 合并并去重 |
| UNION ALL | 合并但不去重 |
| ORDER BY | 通常放在整个合并查询的末尾 |
| LIMIT | 可限制最终合并结果的行数 |
6.8 插入查询结果
6.8.1 插入查询结果的概念
可以将
SELECT查询得到的结果直接插入另一张表,不需要先查询结果再逐条手动插入。
6.8.2 基本语法
sql
-- 将查询结果直接插入目标表
INSERT INTO 目标表名 (字段1, 字段2, 字段3)
SELECT 字段1, 字段2, 字段3
FROM 来源表
WHERE 查询条件;
6.8.3 将 C++001 班的学生复制到 student1 表
sql
-- 查询 C++001 班的学生信息
-- 将查询结果批量插入 student1 表
INSERT INTO student1 (name, sno, age, gender, enroll_date, class_id)
SELECT
s.name,s.sno,s.age,s.gender,
s.enroll_date,s.class_id
FROM student AS s,class AS c
WHERE s.class_id = c.id, c.name = 'C++001班';
执行过程:
- 查询学生表和班级表。
- 根据
s.class_id = c.id关联学生与班级。 - 筛选 C++001 班的学生。
- 将查询结果插入
student1。
重点: **INSERT INTO ... SELECT** 可以批量插入符合条件的查询结果。
6.8.4 插入查询结果的注意事项
- 目标表的字段数量应与查询结果的列数一致。
- 对应字段的数据类型应兼容。
- 字段顺序必须正确对应。
- 目标表的主键、唯一性和非空等约束仍然有效。
- 重复执行插入语句可能造成重复业务数据,应根据实际情况进行去重或设置唯一约束。
6.9 根据查询结果创建表
6.9.1 使用 LIKE 复制表结构
CREATE TABLE 表名 LIKE用于根据已有表创建新表,复制表结构和相应的索引定义,但不复制原表的数据。
sql
-- 根据 student 表的结构创建 student1 表
CREATE TABLE student1 LIKE student;
- 如果
student1已经存在,需要先检查表是否存在,避免重复创建时报错。
6.9.2 使用 AS SELECT 根据查询结果创建表
- 可以使用
CREATE TABLE 表名 AS SELECT,根据查询结果创建新表并写入相应的数据。 - 相当于新建一张表结构一样的新表,同时插入了一条数据行。
sql
--创建新表 student_copy,列结构和 SELECT的结果一致
--将 student 表中 class_id = 1 的所有学生记录插入 student_copy
CREATE TABLE student_copy AS
SELECT * FROM student WHERE class_id = 1;
- 也可以只选择需要的字段:
sql
-- 只复制学生姓名、学号和年龄
CREATE TABLE student_info AS
SELECT name, sno, age FROM student;
注意:
CREATE TABLE 表名 LIKE侧重复制表结构。CREATE TABLE 表名 AS SELECT侧重根据查询结果创建并填充数据。CREATE TABLE 表名 AS SELECT不等同于完整复制原表的主键、索引和所有约束。
如果需要先复制表结构,再插入符合条件的数据,可以使用:
sql
重点:复制表结构、复制查询结果、插入查询结果是不同的操作,需要根据需求选择对应语句。
6.10 本章核心知识点总结
6.10.1 多表查询与连接
| 知识点 | 核心内容 |
|---|---|
| 笛卡尔积 | 多表记录进行组合,记录数量可能迅速增加 |
| 连接条件 | 根据表之间的关联字段筛选有效记录 |
| 字段歧义 | 使用 表名.字段名 明确指定字段 |
| 表名别名 | 使用 s、c 等别名简化 SQL |
| 内连接 | 只返回满足连接条件的记录 |
| 左外连接 | 保留左表全部记录 |
| 右外连接 | 保留右表全部记录 |
| 自连接 | 同一张表使用不同别名进行连接 |
6.10.2 子查询
| 知识点 | 核心内容 |
|---|---|
| 单行子查询 | 内层查询返回一个值 |
| 多行子查询 | 内层查询返回多行数据,常用 IN、NOT IN |
| 多列子查询 | 多个字段组合进行匹配 |
| FROM 子查询 | 将查询结果作为临时结果集,必须指定别名 |
6.10.3 合并与数据操作
| 知识点 | 核心内容 |
|---|---|
| UNION | 合并查询结果并去重 |
| UNION ALL | 合并查询结果但不去重 |
| 合并列数 | 各查询返回的列数必须一致 |
| 合并数据类型 | 对应列的数据类型应兼容 |
| ORDER BY | 对整个合并结果进行排序 |
| LIMIT | 限制最终合并结果的行数 |
| INSERT INTO ... SELECT | 将查询结果插入已有表 |
| CREATE TABLE ... LIKE | 复制表结构,不复制数据 |
| CREATE TABLE ... AS SELECT | 根据查询结果创建并填充新表 |
6.10.4 本章必须掌握的 SQL 语句
sql
-- 1. 多表查询:使用 WHERE 指定关联条件
SELECT *
FROM student s, class c --生成笛卡尔积
WHERE s.class_id = c.id;--筛选无关联的笛卡尔积
-- 2. 内连接:查询学生及其成绩
SELECT s.name, sc.score
FROM student s
JOIN score sc
ON s.id = sc.student_id;
-- 3. 左外连接:查询没有成绩的学生
SELECT s.*
FROM student s
LEFT JOIN score sc
ON s.id = sc.student_id
WHERE sc.student_id IS NULL;
-- 4. 右外连接:查询没有学生的班级
SELECT c.*
FROM student s
RIGHT JOIN class c
ON s.class_id = c.id
WHERE s.id IS NULL;
-- 5. 自连接:查询 MySQL 成绩高于 Java 成绩的记录
SELECT s1.*
FROM score s1
JOIN score s2
ON s1.student_id = s2.student_id
WHERE s1.course_id = 3
AND s2.course_id = 1
AND s1.score > s2.score;
-- 6. 单行子查询:查询与"不想毕业"同班的学生
SELECT *
FROM student
WHERE class_id = (
SELECT class_id
FROM student
WHERE name = '不想毕业'
);
-- 7. 多行子查询:查询 Java 或 MySQL 课程的成绩
SELECT *
FROM score
WHERE course_id IN (
SELECT id
FROM course
WHERE name IN ('Java', 'MySQL')
);
-- 8. 多列子查询:查询重复录入的成绩组合
SELECT *
FROM score
WHERE (score, student_id, course_id) IN (
SELECT score, student_id, course_id
FROM score
GROUP BY score, student_id, course_id
HAVING COUNT(*) > 1
);
-- 9. 合并查询并去重
SELECT id, name FROM student_a
UNION
SELECT id, name FROM student_b;
-- 10. 合并查询但不去重
SELECT id, name FROM student_a
UNION ALL
SELECT id, name FROM student_b;
-- 11. 插入查询结果
INSERT INTO student1
(name, sno, age, gender, enroll_date, class_id)
SELECT
name, sno, age, gender, enroll_date, class_id
FROM student
WHERE class_id = 2;
-- 12. 复制表结构
CREATE TABLE student_copy LIKE student;
-- 13. 根据查询结果创建并填充新表
CREATE TABLE student_info AS
SELECT name, sno, age
FROM student;
6.10.5 本章易错点
- 多表查询必须明确连接条件。 如果遗漏关联条件,可能产生笛卡尔积。
- 同名字段必须明确所属表。 使用
student.name或s.name避免字段歧义。 - 内连接不会保留未匹配记录。 查询没有成绩的学生,应考虑左外连接。
- 判断空值使用
**IS NULL**或**IS NOT NULL**。 不要使用= NULL。 - 自连接必须设置不同别名。 例如
score s1和score s2。 - 单行子查询和多行子查询的适用场景不同。 单个值可以使用
=,多行结果通常使用IN。 - UNION 自动去重,UNION ALL 不去重。
- 合并查询要求列数一致、对应列类型兼容。 字段名称可以不同,但按位置匹配。
- ORDER BY 通常放在整个合并查询的末尾。 注意它对最终结果集的作用范围。
- INSERT INTO 表名 SELECT 的列数和顺序必须正确对应。
- CREATE TABLE 表名 LIKE / CREATE TABLE 表名 AS SELECT 的用途不同。 前者复制表结构,后者根据查询结果创建并填充数据。