子查询与集合运算第 6 章 · 第五、六节

一条查询里,
藏着另一条查询

子查询就是"先查一次,再拿结果查第二次"。
今天用逐步动画,把每条 SQL 拆开看数据怎么变。

课件里按 → / 空格 走下一步,← 退回上一步

外层查询
SELECT … FROM tc WHERE cno = ( ? )
子查询(括号里)
SELECT cno FROM c WHERE cn = '数据库系统'
🧭 先判断:遇到什么问题,用什么工具? 点击卡片翻面
"数据库系统是哪些老师上的?"手上只有课程名,授课表只有课程号
普通子查询(=)条件值要先查出来,且只有 1 个
"苏乐选了哪些课?列出课名"她选了好几门
子查询 + IN先查出的是一组值
"谁比计算机专业所有老师工资都高?"要和 4 个工资比
子查询 + ALL和一组值比大小
"每个学生是否选了 c1?"每个人要分别判断
相关子查询 + EXISTS答案随外层每一行变化
"c1、c2 的学生合成一份名单"两份结果、不能重复
UNION合并两个查询结果并去重
1

返回一个值的子查询

由课程名"数据库系统",查出谁讲这门课

👩‍💼
📍 场景 · 教务处"数据库系统这门课是哪几位老师上的?我要通知他们开教研会。"
难点:授课表 tc 里只有课程号,没有课程名。我们手上只有"数据库系统"这个名字。
笨办法:分两次查,中间靠人抄
  1. 先查 SELECT cno FROM c WHERE cn='数据库系统' → 抄下 c5
  2. 再手动写 SELECT tno FROM tc WHERE cno='c5'
换一门课又要重来一遍,抄错了结果就错。
把第 1 次查询直接塞进第 2 次查询的括号里,数据库自动"先查号、再查人",一条语句搞定。
👉 判断信号:条件里需要的值,要先查出来才知道。
执行顺序:先执行括号里的子查询,得到的结果替换掉整个括号,再执行外层。
📌 记住
  • 子查询要写在 括号 里
  • 它返回 1 个值,外层可以直接用 =、>、< 比较
💡 写法技巧
  • 先把括号里那句单独运行,看结果对不对
  • 对了再套进外层
2

返回一组值:用 IN

查学生 s2 选修的课程名

🧑‍🏫
📍 场景 · 辅导员"把苏乐(s2)这学期选的课,课程名都列出来,我要核对学分。"
难点:选课表 sc 只有课程号,而且苏乐选了好几门——子查询会返回一组值。
笨办法:先查出 c4、c5、c6、c7,再手写
WHERE cno='c4' OR cno='c5' OR cno='c6' OR cno='c7'
选了 10 门就要写 10 个 OR,下学期选课一变全部重写。
用 IN (子查询):子查询查出几门就自动匹配几门,不用管具体有几个值。
👉 判断信号:先查出来的是一份"名单"(多个值)。
子查询返回多个值时,外层要用 IN(等于其中任意一个)。用 = 会报错 1242。
📌 口诀
  • 一个值 → =
  • 一组值 → IN
  • IN 和 = ANY 效果一样,常用 IN
⚠️ 易错点
  • 子查询的 SELECT 后面只能写 1 个字段
  • 写成 SELECT cno, cn 会报错 1241
3

和一组值比大小:ANY 与 ALL

其他专业中,工资比计算机专业教师高的人

📊
📍 场景 · 人事处"做薪资对比:其他专业里,有谁比计算机专业某位老师工资高?又有谁比计算机专业所有老师都高?"
难点:计算机专业有 4 位老师、4 个工资,要拿一个人的工资和一组工资比大小。
笨办法:先查出 4 个工资,人工找出最低 2800、最高 4100,再写 sal > 2800。工资一调整,数字就过时了。
> ANY (子查询) 自动"和最低的比",> ALL (子查询) 自动"和最高的比",数据变了结果也跟着变。
👉 判断信号:和"一组值"比大小——"某一个" 用 ANY,"每一个" 用 ALL。
> ANY:比其中某一个大就行 = 大于最小值 | > ALL:比每一个都大 = 大于最大值
写法白话等价于
> ANY (…)比其中某一个大> 最小值
> ALL (…)比每一个都大> 最大值
< ANY (…)比其中某一个小< 最大值
< ALL (…)比每一个都小< 最小值
4

相关子查询与 EXISTS

查选修了 c1 的学生

📣
📍 场景 · 教务处"c1 要调课了,请给每一个选了 c1 的学生发通知;没选的同学,提醒他们还可以补选。"
难点:问题是针对每个学生问的——"他选没选 c1?" 王彤要查王彤的选课,苏乐要查苏乐的,每个人的子查询都不一样。
🏫 生活类比:班主任拿着花名册逐个点名:"王彤,你选 c1 了吗?""苏乐,你呢?"
普通子查询做不到:普通子查询只执行一次、得到一个固定结果,没法"对每个人分别问一次"。
手工做的话,8 个学生就要查 8 次。
相关子查询:子查询里写 sno = s.sno("当前这个学生"),外层每取一个学生就自动问一次;EXISTS 只回答"有 / 没有"。
👉 判断信号:问题里出现"每一个……是否……",答案随每一行变化。
子查询里用到了外层表的字段(s.sno),就是相关子查询:外层每取一行,子查询就执行一次。EXISTS 只看子查询有没有查到行。
普通子查询相关子查询
用到外层字段?没有有(如 s.sno)
能单独运行?能不能
执行几次?1 次外层有几行就几次
📌 EXISTS 记住
  • 查到 ≥1 行 → 真;一行没有 → 假
  • 只关心"有没有",所以写 SELECT *
  • NOT EXISTS 正好相反
  • 子查询结果含 NULL 时 NOT IN 会查不出任何行,用 NOT EXISTS 更稳
5

集合运算:UNION

选修了 c1 或 c2 的学生

✉️
📍 场景 · 学院办公室"c1 和 c2 两门课要合并上实验课,给这两门课的学生发同一封邮件。名单要合成一份,同一个人不能收到两封。"
难点:这是两份名单,要上下合成一份,还要去掉重复的人(王彤两门都选了)。
笨办法:分别导出两份名单,在 Excel 里复制粘贴、手动删重复,人一多就容易漏。
UNION 把两个查询结果直接拼起来并自动去重;如果要保留重复(比如统计人次),用 UNION ALL。
👉 判断信号:要把"两份结果"合成"一份"。
UNION 把两个查询结果上下拼接并去重;UNION ALL 保留重复。两边列数相同、类型对应。
📌 规则
  • 每个 SELECT 的列数必须相同
  • 对应列的数据类型要一致
  • 结果的列名取第一个查询的列名
💡 扩展
  • 带 GROUP BY 的统计查询也能用 UNION 拼起来
  • 例:c1、c2 各自求总分/平均分,再合并成一张表
6

子查询用于修改数据了解

教师 t1 讲授的课程,课时都增加 16

🛠️
📍 场景 · 教务处"t1 老师带的课都升级成实验课,每门课课时 +16。"
难点:课程表 c 里没有教师信息,不知道 t1 教哪几门,要先去授课表查。
笨办法:先查出 c1、c2,再一条条写 UPDATE,漏改一门就出错。
在 UPDATE 的 WHERE 里放子查询:先查出要改哪些课,再统一修改。
👉 判断信号:要修改的行,需要先查别的表才能确定。
UPDATE 的 WHERE 条件里也能放子查询。改之前先用 SELECT 确认要改哪几行。
同理: INSERT INTO 新表 SELECT … 可以把查询结果直接存进另一张表(不写 VALUES,列数和顺序要对应)。
✓

课堂小测

点击选项,立即看对错与解析

一页总结
①先里后外括号里的结果替换整个括号
②一个值 = ,一组值 IN用错报 1242
③>ANY 比最小大,>ALL 比最大大转成和一个数比较
④相关子查询逐行执行EXISTS 只看有没有
⑤UNION 去重,ALL 不去重列数、类型要对应
⑥增改也能用子查询先 SELECT 再改