考点频率 :★★★★★(下午题必考,选择题常考,子查询是SQL查询的进阶核心)
难度 :⭐⭐⭐⭐
建议:重点掌握子查询的分类、IN与EXISTS的区别、ANY与ALL与比较运算符的搭配
1️⃣ 什么是嵌套查询?
嵌套查询(子查询,Subquery) 是指在一个 SELECT 语句中嵌套另一个 SELECT 语句 。外层的查询称为外层查询(父查询) ,内层的查询称为子查询。
打个比方:你是一个导演,要选一个演员。你先问"哪些人通过了初试?"(子查询),得到一份名单后,再问"名单里谁演过古装剧?"(外层查询)。子查询的结果被外层查询当作"数据源"或"条件值"来使用。
子查询的分类
| 分类方式 | 类型 | 说明 |
|---|---|---|
| 按结果集大小 | 标量子查询 | 返回单个值(一行一列) |
| 行子查询 | 返回一行(多列) | |
| 表子查询 | 返回多行多列(一张表) | |
| 按相关性 | 不相关子查询 | 子查询独立于外层查询,可单独执行 |
| 相关子查询 | 子查询依赖外层查询的当前行值,不能单独执行 |
2️⃣ 带 IN 的子查询
IN 用于判断某个值是否在子查询返回的集合中。
语法:
sql
SELECT 列名 FROM 表名
WHERE 列名 IN (子查询);
示例:查询选修了"数学"课程的学生姓名
sql
SELECT 姓名 FROM 学生表
WHERE 学号 IN (
SELECT 学号 FROM 选课表
WHERE 课程号 = (SELECT 课程号 FROM 课程表 WHERE 课程名 = '数学')
);
执行逻辑:先执行最内层子查询得到"数学"的课程号,再执行中层子查询得到选修了该课程的所有学号,最后外层查询筛选出这些学号对应的学生姓名。
NOT IN 表示不在集合中。
示例:查询没有选修任何课程的学生姓名
sql
SELECT 姓名 FROM 学生表
WHERE 学号 NOT IN (
SELECT DISTINCT 学号 FROM 选课表
);
3️⃣ 带 EXISTS 的子查询
EXISTS 用于判断子查询是否返回了至少一行数据。它只关注"有没有数据",不关心具体返回什么。
语法:
sql
SELECT 列名 FROM 表名
WHERE EXISTS (子查询);
示例:查询选修了"数学"课程的学生姓名(用 EXISTS 实现)
sql
SELECT 姓名 FROM 学生表 S
WHERE EXISTS (
SELECT * FROM 选课表 SC
WHERE SC.学号 = S.学号
AND SC.课程号 = (SELECT 课程号 FROM 课程表 WHERE 课程名 = '数学')
);
注意 :子查询中的
SELECT *是惯用写法 ,因为 EXISTS 只判断存在性,不关心具体列。写成SELECT 1或SELECT *效果一样。
执行逻辑:对于外层查询的每一行,执行子查询检查是否存在满足条件的记录。如果子查询返回至少一行,EXISTS 为真,该行被包含在结果中。
4️⃣ IN vs EXISTS(核心区别,必考!)
这是软考选择题的高频考点:
| 对比项 | IN |
EXISTS |
|---|---|---|
| 子查询类型 | 不相关子查询(优先执行子查询,得到完整结果集) | 相关子查询(通常与外层查询关联) |
| 判断依据 | 判断值是否在子查询结果集合中 | 判断子查询是否有返回行(存在性) |
| 子查询结果集 | 需要完整结果集(执行完所有子查询) | 找到第一行匹配即停止(短路求值) |
| 处理NULL | IN 遇到 NULL 可能导致整个结果为空 |
EXISTS 不受 NULL 影响(只关心行存在性) |
| 外层查询数据量大时 | 子查询结果集小则效率高 | 外层每行都要执行子查询,可能较慢 |
| 子查询数据量大时 | 子查询结果集大则效率低 | 利用索引匹配时效率高 |
| SELECT 子句 | 必须返回一列(用于比较) | 可以是任意列(通常写 * 或 1) |
选择建议:
- 子查询结果集较小 (外层表大)→ 用
IN(先执行子查询,得到结果集,再与外层匹配)- 子查询结果集较大 (内层表大)→ 用
EXISTS(找到匹配即停止,利用索引)- 需要关联外层表 的查询 → 只能用
EXISTS(IN的子查询不能直接引用外层表的列)
5️⃣ 带 ANY / ALL 的子查询
ANY 和 ALL 与比较运算符 (=、>、<、>=、<=、<>)结合使用,用于将某个值与子查询返回的一组值进行比较。
| 运算符 | 含义 | 相当于 |
|---|---|---|
= ANY |
等于子查询结果中的任意一个 | 等价于 IN |
> ANY |
大于 子查询结果中的最小值 | 比最小的还大 |
< ANY |
小于 子查询结果中的最大值 | 比最大的还小 |
> ALL |
大于 子查询结果中的最大值 | 比最大的还大 |
< ALL |
小于 子查询结果中的最小值 | 比最小的还小 |
<> ANY |
不等于子查询结果中的任意一个 | 不等于某个值(几乎无意义) |
<> ALL |
不等于子查询结果中的全部 | 等价于 NOT IN |
示例:
sql
-- 查询成绩高于"所有"学生平均成绩的学生
SELECT * FROM 选课表
WHERE 成绩 > ALL (SELECT AVG(成绩) FROM 选课表 GROUP BY 学号);
-- 实际上这样写有问题:ALL后面应该跟一个单列集合,子查询返回多个平均值后,> ALL表示大于其中最大的那个
更准确的示例:
sql
-- 查询比"所有"计算机系学生年龄都大的学生
SELECT * FROM 学生表
WHERE 年龄 > ALL (SELECT 年龄 FROM 学生表 WHERE 系别 = '计算机系');
含义:年龄 > 计算机系所有人中的最大年龄 → 比计算机系最年长的还年长。
sql
-- 查询比"至少一个"计算机系学生年龄大的学生(即比计算机系最年轻的人还大)
SELECT * FROM 学生表
WHERE 年龄 > ANY (SELECT 年龄 FROM 学生表 WHERE 系别 = '计算机系');
含义:年龄 > 计算机系至少一个人的年龄 → 比计算机系最年轻的还年长。
6️⃣ 相关子查询 vs 不相关子查询
| 对比项 | 不相关子查询 | 相关子查询 |
|---|---|---|
| 独立性 | 子查询可以独立执行,不依赖外层 | 子查询依赖外层的当前行值 |
| 执行次数 | 子查询执行一次 | 子查询执行多次(外层每行一次) |
| 典型用法 | 子查询返回一个固定集合,外层用 IN/= 判断 |
子查询中使用外层表的列作为条件,用 EXISTS |
| 性能 | 通常较快 | 可能较慢(每次都要执行子查询) |
7️⃣ 经典例题
例题1:用 SQL 查询"选修了所有课程"的学生学号。
解析 :这是一个典型的"所有"查询,用 NOT EXISTS 实现双否定。
sql
SELECT 学号 FROM 学生表 S
WHERE NOT EXISTS (
SELECT * FROM 课程表 C
WHERE NOT EXISTS (
SELECT * FROM 选课表 SC
WHERE SC.学号 = S.学号 AND SC.课程号 = C.课程号
)
);
逻辑:不存在任何一门课程,该学生没选。即"所有课程都选了"。
例题2 :以下 SQL 语句中,= ANY 等价于( )。
A. IN
B. NOT IN
C. EXISTS
D. NOT EXISTS
解析 := ANY 表示等于子查询结果中的任意一个值,等价于 IN。选 A。
例题3 (判断):EXISTS 子查询中,SELECT 子句可以写任意列,因为 EXISTS 只判断是否有返回行,不关心具体值。( )
解析 :正确。EXISTS 只关心子查询是否返回了数据行,不关心具体列值,因此 SELECT *、SELECT 1、SELECT 学号 效果相同。
8️⃣ 记忆口诀
IN在集合中,EXISTS有没有行。
= ANY就是IN,<> ALL就是NOT IN。
> ANY大于最小,> ALL大于最大。
< ANY小于最大,< ALL小于最小。
EXISTS 相关子查询口诀:
外层每行查一次,关联条件要写对。
NOT EXISTS套两层,就是"全选"反过来。
9️⃣ 小测验(评论区对答案)
SQL 语句:
SELECT 姓名 FROM 学生表 WHERE 年龄 > ANY (SELECT 年龄 FROM 学生表 WHERE 系别 = '计算机系')该语句的含义是( )。
A. 查询比所有计算机系学生年龄都大的学生
B. 查询比至少一个计算机系学生年龄大的学生
C. 查询年龄和计算机系学生都不同的学生
D. 查询年龄大于计算机系学生平均年龄的学生
🔔 本专栏日更,点击头像 → 专栏《软考中级高频考点》订阅,第一时间接收新内容
#软考中级 #软件设计师 #SQL #嵌套查询 #IN #EXISTS #ANY #ALL #数据库技术基础 #软考备考