六、联合查询

6.1 本章目标

  1. 了解联合查询的计算过程。
  2. 掌握内连接、左外连接、右外连接和自连接。
  3. 掌握子查询和合并查询。
  4. 掌握插入查询结果以及根据查询结果创建表。

6.2 联合查询简介

6.2.1 为什么需要联合查询

在数据库设计过程中,由于数据库范式的要求,数据通常被拆分到多个表中。

例如:

  • student:学生表,保存学生基本信息。
  • class:班级表,保存班级信息。
  • course:课程表,保存课程信息。
  • score:成绩表,保存学生的课程成绩。

如果需要查询学生的姓名、班级、课程和成绩,就需要从多张表中获取数据,并根据表之间的关联关系进行组合。
联合查询的核心:将多个表组合起来,从多个表中获取所需要的数据。

6.2.2 多表联合查询的计算过程

多表查询可以按照以下过程理解:

  1. 确定参与查询的所有数据表。
  2. 对参与查询的表进行组合,形成笛卡尔积。
  3. 根据表之间的关联关系,过滤不符合条件的记录。
  4. 根据其他查询条件进一步筛选数据。
  5. 根据需要选择字段、分组和排序。
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 = '唐三藏';

执行过程:

  1. 确定参与查询的表:student 和 score。
  2. 根据 sc.student_id = s.id 关联学生和成绩。
  3. 使用 WHERE 筛选姓名为唐三藏的记录。
  4. 返回学生姓名和成绩。

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

执行过程:

  1. 以学生表为左表。
  2. 连接成绩表。
  3. 保留所有学生。
  4. 筛选成绩表中没有匹配记录的数据。

这里的 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 自连接的概念

自连接是将同一张表当作两张不同的表进行连接查询。
其本质仍然是表连接,只不过参与连接的是同一张物理表。
自连接可以用于:

  1. 比较同一张表中不同记录的数据。
  2. 实现行与行之间的比较。
  3. 查询具有上下级关系的数据。
  4. 比较同一学生不同课程的成绩。

重点:自连接需要为同一张表指定不同的别名。

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';

执行逻辑:

  1. s1 和 s2 分别表示同一学生的两条成绩记录。
  2. 根据学生编号匹配同一学生。
  3. 连接课程表,识别两条成绩分别属于哪门课程。
  4. 筛选 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 = '不想毕业'
);

执行过程:

  1. 内层查询获取"不想毕业"的班级编号。
  2. 外层查询查找班级编号相同的学生。
  3. 返回同班学生的完整信息。

重点:使用 **=** 时,子查询应返回一个值。如果返回多行,通常会发生错误。

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'
);

执行过程:

  1. 内层查询获取 Java 和 MySQL 的课程编号。
  2. 外层查询判断 course_id 是否在结果集中。
  3. 返回符合条件的成绩记录。

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班';

执行过程:

  1. 查询学生表和班级表。
  2. 根据 s.class_id = c.id 关联学生与班级。
  3. 筛选 C++001 班的学生。
  4. 将查询结果插入 student1。

重点: **INSERT INTO ... SELECT** 可以批量插入符合条件的查询结果。

6.8.4 插入查询结果的注意事项

  1. 目标表的字段数量应与查询结果的列数一致。
  2. 对应字段的数据类型应兼容。
  3. 字段顺序必须正确对应。
  4. 目标表的主键、唯一性和非空等约束仍然有效。
  5. 重复执行插入语句可能造成重复业务数据,应根据实际情况进行去重或设置唯一约束。

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 本章易错点

  1. 多表查询必须明确连接条件。 如果遗漏关联条件,可能产生笛卡尔积。
  2. 同名字段必须明确所属表。 使用 student.name 或 s.name 避免字段歧义。
  3. 内连接不会保留未匹配记录。 查询没有成绩的学生,应考虑左外连接。
  4. 判断空值使用 **IS NULL** 或 **IS NOT NULL**。 不要使用 = NULL。
  5. 自连接必须设置不同别名。 例如 score s1 和 score s2。
  6. 单行子查询和多行子查询的适用场景不同。 单个值可以使用 =,多行结果通常使用 IN。
  7. UNION 自动去重,UNION ALL 不去重。
  8. 合并查询要求列数一致、对应列类型兼容。 字段名称可以不同,但按位置匹配。
  9. ORDER BY 通常放在整个合并查询的末尾。 注意它对最终结果集的作用范围。
  10. INSERT INTO 表名 SELECT 的列数和顺序必须正确对应。
  11. CREATE TABLE 表名 LIKE / CREATE TABLE 表名 AS SELECT 的用途不同。 前者复制表结构,后者根据查询结果创建并填充数据。
相关推荐
l1t1 小时前
一个clickhouse易错的语法问题
数据库·sql·clickhouse
蓝胖的四次元口袋1 小时前
Redis 知识梳理(4)
数据库·redis
foolishlee1 小时前
Neon compute GetPage
数据库
对空六课3 小时前
两个统计平台数据不同:用同一时间窗和事件样本做差异定位
大数据·前端·网络·数据库
wno7048 小时前
postgresSQL压缩版安装与基本命令
数据库·postgresql
聚美智数10 小时前
智能结构化识别-图片结构解析-图像结构化解析-照片结构化解析
android·java·数据库
瀚高PG实验室10 小时前
瀚高数据库常用系统表查询入门指南(附常用SQL)
数据库·sql·postgresql·瀚高数据库
ben9518chen11 小时前
mysql数据库基本操作
数据库·mysql
2603_9651481111 小时前
电商选品调研:通过关键词搜索API获取市场热销商品,结合商品详情API分析竞品卖点与定价
开发语言·数据库·人工智能·python·信息可视化