一层套一层的“嵌套查询”到底怎么写?
在实际的数据库应用中,我们常常会遇到这种需求:查询的条件并非一个明确的已知值,而是需要通过另一次查询才能得到。
在 SQL 语言中,一个 SELECT-FROM-WHERE 语句称为一个查询块。将一个查询块嵌套在另一个查询块的 WHERE 子句或 HAVING 短语的条件中的查询,就称为嵌套查询(Nested Query)或子查询。包含子查询的语句则被称为外层查询或父查询。
为了直观地展示嵌套查询是如何“由内向外”一层层剥开的,我们先准备以下三张具体的数据表:
1. 准备具体数据表
表1:Student(学生表)
| Sno (学号) | Sname (姓名) | Age (年龄) | Dept (所在系) |
|---|---|---|---|
| 03090101 | 张功杰 | 19 | 信息系统 |
| 05090102 | 吴肇星 | 19 | 计算机 |
| 05090106 | 王娴 | 18 | 计算机 |
表2:Course(课程表)
| Cno (课程号) | Cname (课程名) | Credit (学分) |
|---|---|---|
| 140001 | 数据库原理 | 3 |
| 140501 | 计算机网络 | 3 |
| 110501 | 会计学 | 2 |
表3:Sc(选课成绩表)
| Sno (学号) | Cno (课程号) | Grade (成绩) |
|---|---|---|
| 03090101 | 140001 | 80 |
| 03090101 | 140501 | 85 |
| 03090101 | 110501 | 75 |
| 05090102 | 140001 | 90 |
| 05090102 | 140501 | 95 |
2. 带有 IN 谓词的子查询
概念:在嵌套查询中,子查询的结果往往是一个集合,因此谓词 IN 是嵌套查询中最经常使用的谓词。它用于判断外层查询的某个属性值是否在内层子查询返回的集合中。
实战需求:查询选修了“数据库原理”(课程号为 140001)课程的学生姓名。
(分析:姓名在 Student 表中,但选课信息在 Sc 表中。我们需要先从 Sc 表查出选了这门课的学号,再把学号喂给 Student 表去查姓名。)
SELECT Sname
FROM Student
WHERE Sno IN (
SELECT Sno
FROM Sc
WHERE Cno = '140001'
);
执行过程与结果:
- 先执行内层查询:
SELECT Sno FROM Sc WHERE Cno = '140001'。对照 Sc 表,系统会找到两条记录,返回一个学号集合:('03090101', '05090102')。 - 再执行外层查询:外层语句变为
SELECT Sname FROM Student WHERE Sno IN ('03090101', '05090102')。 - 最终结果:返回 张功杰 和 吴肇星。
3. 带有比较运算符的子查询
概念:当用户能确切知道内层查询返回的是单值(只有一个数据)时,可以用 >, <, =, >=, <=, !=, <> 等比较运算符将父查询与子查询连接起来。
实战需求:查询“140001”这门课中,成绩高于该门课程平均分的学生学号和成绩。
SELECT Sno, Grade
FROM Sc
WHERE Cno = '140001'
AND Grade > (
SELECT AVG(Grade)
FROM Sc
WHERE Cno = '140001'
);
执行过程与结果:
- 内层查询求单值:求出该课的平均分。
(80 + 90) / 2 = 85。子查询返回单值85。 - 外层查询进行比较:查询退化为
SELECT Sno, Grade FROM Sc WHERE Cno = '140001' AND Grade > 85。 - 最终结果:由于 03090101 考了 80 分(不满足 > 85),只有 05090102 考了 90 分。因此最终返回学号 05090102,成绩 90。
4. 带有 EXISTS 谓词的子查询
概念:EXISTS 代表存在量词。带有 EXISTS 谓词的子查询不返回任何具体的数据记录,而是只产生逻辑真值“true”或逻辑假值“false”。它通常用于判断某个条件下的记录“是否存在”。
实战需求:查询没有选修“140001”课程的学生姓名。
(分析:我们需要利用 NOT EXISTS,如果一个学生在 Sc 表里找不到对应的“140001”选课记录,那么他就是我们要找的人。)
SELECT Sname
FROM Student
WHERE NOT EXISTS (
SELECT *
FROM Sc
WHERE Sc.Sno = Student.Sno
AND Sc.Cno = '140001'
);
执行过程与结果:
这种查询属于相关嵌套查询,由于内层查询的条件 Sc.Sno = Student.Sno 依赖于外层查询,系统必须反复求值:
- 取 Student 表的第一行(张功杰, 03090101),内层执行
SELECT * FROM Sc WHERE Sc.Sno = '03090101' AND Cno = '140001'。结果存在(返回 true)。加上外面的NOT,外层 WHERE 条件判定为 false。张功杰被过滤掉。 - 取 Student 表的第二行(吴肇星, 05090102),内层查询结果同样存在(true),外层判定为 false。吴肇星被过滤掉。
- 取 Student 表的第三行(王娴, 05090106),内层查询结果为空(返回 false)。加上外面的
NOT,外层 WHERE 判定为 true。 - 最终结果:返回 王娴。
避坑指南:写嵌套查询的注意事项
在手写嵌套查询时,请牢记以下铁律:
千万不要在子查询里使用 ORDER BY 子句!SQL 语法规定,子查询的 SELECT 语句中不能使用 ORDER BY 子句,ORDER BY 只能用于对外层主查询的最终结果进行排序。