• 3 min read

一层套一层的“嵌套查询”到底怎么写?


在实际的数据库应用中,我们常常会遇到这种需求:查询的条件并非一个明确的已知值,而是需要通过另一次查询才能得到。

在 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 (成绩)
0309010114000180
0309010114050185
0309010111050175
0509010214000190
0509010214050195

2. 带有 IN 谓词的子查询

概念:在嵌套查询中,子查询的结果往往是一个集合,因此谓词 IN 是嵌套查询中最经常使用的谓词。它用于判断外层查询的某个属性值是否在内层子查询返回的集合中。

实战需求:查询选修了“数据库原理”(课程号为 140001)课程的学生姓名(分析:姓名在 Student 表中,但选课信息在 Sc 表中。我们需要先从 Sc 表查出选了这门课的学号,再把学号喂给 Student 表去查姓名。)

SELECT Sname 
FROM Student 
WHERE Sno IN (
    SELECT Sno 
    FROM Sc 
    WHERE Cno = '140001'
);

执行过程与结果

  1. 先执行内层查询SELECT Sno FROM Sc WHERE Cno = '140001'。对照 Sc 表,系统会找到两条记录,返回一个学号集合:('03090101', '05090102')
  2. 再执行外层查询:外层语句变为 SELECT Sname FROM Student WHERE Sno IN ('03090101', '05090102')
  3. 最终结果:返回 张功杰吴肇星

3. 带有比较运算符的子查询

概念:当用户能确切知道内层查询返回的是单值(只有一个数据)时,可以用 >, <, =, >=, <=, !=, <> 等比较运算符将父查询与子查询连接起来。

实战需求:查询“140001”这门课中,成绩高于该门课程平均分的学生学号和成绩。

SELECT Sno, Grade 
FROM Sc 
WHERE Cno = '140001' 
  AND Grade > (
      SELECT AVG(Grade) 
      FROM Sc 
      WHERE Cno = '140001'
  );

执行过程与结果

  1. 内层查询求单值:求出该课的平均分。(80 + 90) / 2 = 85。子查询返回单值 85
  2. 外层查询进行比较:查询退化为 SELECT Sno, Grade FROM Sc WHERE Cno = '140001' AND Grade > 85
  3. 最终结果:由于 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 依赖于外层查询,系统必须反复求值:

  1. 取 Student 表的第一行(张功杰, 03090101),内层执行 SELECT * FROM Sc WHERE Sc.Sno = '03090101' AND Cno = '140001'。结果存在(返回 true)。加上外面的 NOT,外层 WHERE 条件判定为 false。张功杰被过滤掉。
  2. 取 Student 表的第二行(吴肇星, 05090102),内层查询结果同样存在(true),外层判定为 false。吴肇星被过滤掉。
  3. 取 Student 表的第三行(王娴, 05090106),内层查询结果为空(返回 false)。加上外面的 NOT,外层 WHERE 判定为 true
  4. 最终结果:返回 王娴

避坑指南:写嵌套查询的注意事项

在手写嵌套查询时,请牢记以下铁律: 千万不要在子查询里使用 ORDER BY 子句!SQL 语法规定,子查询的 SELECT 语句中不能使用 ORDER BY 子句,ORDER BY 只能用于对外层主查询的最终结果进行排序。