当前位置: 首页 > news >正文

数据库系统概论(十)SQL 嵌套查询 超详细讲解(附带例题表格对比带你一步步掌握)

数据库系统概论(十)SQL 嵌套查询 超详细讲解(附带例题表格对比带你一步步掌握)

  • 前言
  • 一、什么是嵌套查询?
    • 1. 基础组成:查询块
    • 2. 嵌套的两种常见位置
      • (1)藏在 FROM 子句里(当子查询是一张“临时表”)
      • (2)藏在 WHERE 子句里(用子查询结果作条件)
    • 3. 多层嵌套
    • 4. 子查询的限制:不能用 ORDER BY
  • 二、带有 IN 谓词的子查询
    • 1. IN谓词的核心作用
    • 2. 子查询 vs 连接查询
        • (1)连接查询(表关联法)
          • (2)子查询(分步筛选法)
    • 3. 不相关子查询
    • 4. IN子查询的常见用法场景
  • 三、带有比较运算符的子查询
    • 1. 带比较运算符的子查询是什么?
    • 2. 子查询的分类
      • (1)不相关子查询
      • (2)相关子查询
    • 3. 经典案例:查每门课的最高分学生
  • 四、带有ANY(SOME)或ALL谓词的子查询
    • 1. 什么是ANY和ALL?
    • 2. ANY/ALL怎么用?
    • 3. 经典案例
      • 案例1:查非计算机专业中,比计算机专业任意一个学生年龄小的学生(出生日期更晚)
      • 案例2:查非计算机专业中,比计算机专业所有学生年龄都小的学生(出生日期更晚)
    • 4. ANY/ALL和聚集函数的等价表
  • 五、带有 EXISTS 谓词的子查询
    • 1. EXISTS谓词的基本概念
    • 2. 经典案例
      • 案例1:查询选修了81001号课程的学生姓名。
      • 案例2:查询没有选修81001号课程的学生姓名。
    • 3. EXISTS与IN的对比
    • 4. 用EXISTS实现全称量词查询
      • 案例:查询选修了全部课程的学生姓名。


前言

  • 在前几期博客中,我们探讨了 SQL 连接查询和单表查询技术。
  • 从本节开始,我们将深入讲解 SQL 中嵌套查询的知识点。

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的数据库系统概论专栏
https://blog.csdn.net/2402_83322742/category_12911520.html?spm=1001.2014.3001.5482


一、什么是嵌套查询?

核心概念
嵌套查询就像“大问题拆分成小问题”,把一个查询(小问题)藏在另一个查询(大问题)里,层层解决。

1. 基础组成:查询块

  • 查询块:一个完整的 SELECT-FROM-WHERE 语句,比如:
    SELECT Sno FROM SC WHERE Cno='81001'  -- 小问题:查选了81001课程的学号
    
  • 嵌套:把小问题的结果,当作大问题的条件。大问题叫 外层查询(父查询),小问题叫 内层查询(子查询)

2. 嵌套的两种常见位置

(1)藏在 FROM 子句里(当子查询是一张“临时表”)

示例

SELECT Sname  -- 大问题:查学生姓名
FROM Student, (SELECT Sno FROM SC WHERE Cno='81001') AS SC1  -- 小问题:先查选了81001课的学号,当作临时表SC1
WHERE Student.Sno=SC1.Sno;  -- 用临时表SC1的学号,匹配学生表找姓名

逻辑先得到选了81001课的学号列表(SC1),再从学生表中找到这些学号对应的姓名

(2)藏在 WHERE 子句里(用子查询结果作条件)

示例

SELECT Sname  -- 大问题:查学生姓名
FROM Student 
WHERE Sno IN (SELECT Sno FROM SC WHERE Cno='81002');  -- 小问题:查选了81002课的学号,用IN判断是否在其中

逻辑先查选了81002课的学号,再在学生表中筛选出学号在这个列表里的学生姓名

3. 多层嵌套

特点:子查询里还可以再套子查询(最多嵌套多少层?SQL标准没限制,但实际别写太复杂),像剥洋葱一样层层查询
示例

SELECT Sname  -- 大问题:查“所有课程都选了”的学生姓名
FROM Student 
WHERE NOT EXISTS (  -- 外层:不存在“该学生没选的课程”SELECT * FROM Course WHERE NOT EXISTS (  -- 内层:不存在“该课程没被该学生选”SELECT * FROM SC WHERE Sno= Student.Sno  -- 学生学号AND Cno= Course.Cno  -- 课程编号)
);

逻辑

  1. 外层 NOT EXISTS:找“不存在没选的课程”的学生。
  2. 内层 NOT EXISTS:对每门课程,检查“学生是否没选这门课”。
  3. 两层结合:如果所有课程都被学生选了(内层都不存在没选的情况),外层就会选中该学生。

4. 子查询的限制:不能用 ORDER BY

  • 原因:子查询只是给外层提供数据(比如学号列表),不需要排序,排序留给外层做。
  • 正确做法:
    SELECT Sname 
    FROM Student 
    WHERE Sno IN (SELECT Sno FROM SC WHERE Cno='81002'  -- 内层不排序
    )
    ORDER BY Sname;  -- 排序放在外层
    

二、带有 IN 谓词的子查询

1. IN谓词的核心作用

一句话理解

IN 就像“在名单里”,判断某个值是否在子查询返回的集合中。

场景举例

想查“选了‘信息系统概论这门课的学生”,可以分两步:

  1. 先查这门课的课程号(比如81004)→ 得到一个“课程号名单”。
  2. 再查选了这个课程号的学生学号 → 得到“学号名单”。
  3. 最后从学生表中找出学号在“学号名单”里的学生 → 用 IN 判断是否在名单中。

2. 子查询 vs 连接查询

相同目标:查选修“信息系统概论”的学生学号和姓名。

(1)连接查询(表关联法)
SELECT Sno, Sname 
FROM Student 
JOIN SC ON Student.Sno = SC.Sno  -- 学生表和选课表用学号关联
JOIN Course ON SC.Cno = Course.Cno  -- 选课表和课程表用课程号关联
WHERE Course.Cname = '信息系统概论';  -- 直接通过表关联找到对应课程的学生

逻辑:把三张表“拼”在一起,像拼图一样直接找到符合条件的行。

(2)子查询(分步筛选法)
SELECT Sno, Sname 
FROM Student 
WHERE Sno IN (  -- 学号是否在“选了这门课的学号名单”里?SELECT Sno FROM SC WHERE Cno IN (  -- 课程号是否在“信息系统概论的课程号名单”里?SELECT Cno FROM Course WHERE Cname='信息系统概论'  -- 先查课程号)
);

逻辑

  • 最内层:先查“信息系统概论”的课程号(比如81004)→ 得到课程号名单。
  • 中间层:再查选了这个课程号的学号 → 得到学号名单。
  • 最外层:从学生表中筛选学号在名单里的学生 → 用 IN 做判断。

对比总结

  • 连接查询:适合“直接拼表关联”的简单场景,性能可能更高。
  • 子查询:适合“分步解决问题”的逻辑,思路更清晰,尤其适合多层筛选。

3. 不相关子查询

定义:子查询的条件不依赖父查询,可以独立运行,结果直接给父查询用。
特点

  • 子查询先执行,结果是一个固定的集合(比如学号列表、课程号列表)。
  • 父查询再用这个集合做条件筛选。

示例:查平均分高于85分的学生

SELECT Sno, Sname, Smajor  -- 父查询:从学生表找学生
FROM Student 
WHERE Sno IN (  SELECT Sno FROM SC  -- 子查询:先查平均分>85的学号GROUP BY Sno  -- 按学号分组,算每个学生的平均分HAVING AVG(grade) > 85  
);

执行步骤

  1. 子查询独立运行:按学号分组,算出平均分>85的学号列表(如{001, 003})。
  2. 父查询用 IN 判断学生学号是否在列表里,返回结果。

4. IN子查询的常见用法场景

场景描述示例SQL(简化版)
查选了某课程的学生SELECT 姓名 FROM 学生 WHERE 学号 IN (SELECT 学号 FROM 选课 WHERE 课程号='81002')
查某类课程的学生信息多层IN嵌套,先查课程号,再查学号
查分组统计后的结果(如平均分)SELECT ... WHERE 学号 IN (SELECT 学号 FROM 选课 GROUP BY 学号 HAVING AVG(成绩)>85)

三、带有比较运算符的子查询

1. 带比较运算符的子查询是什么?

核心思路:子查询的结果是一个单一值(比如一个数字、一个名字),这时可以用 > < = 等比较运算符,把这个单一值和父查询的数据做对比

举个例子

想查和“刘晨”同一个专业的学生。

  • 子查询先找出刘晨的专业:SELECT Smajor FROM Student WHERE Sname = '刘晨',结果是一个值(比如“计算机”)。
  • 父查询用 = 比较,找出专业等于这个值的学生:
    SELECT Sno, Sname, Smajor 
    FROM Student
    WHERE Smajor = (子查询结果)
    

注意!
子查询必须跟在比较符后面不能写反
❌ 错误写法:WHERE (子查询) = Smajor
✅ 正确写法:WHERE Smajor = (子查询)

2. 子查询的分类

(1)不相关子查询

  • 特点:子查询的条件不依赖父查询,可以先独立运行子查询,再把结果传给父查询。
  • 执行顺序:先算子查询 → 得到结果 → 再用结果执行父查询。
  • 例子:查刘晨同系的学生(子查询只需要查一次“刘晨的系”)。
    SELECT Sno, Sname, Sdept 
    FROM Student
    WHERE Sdept = (SELECT Sdept FROM Student WHERE Sname = '刘晨')
    

(2)相关子查询

  • 特点:子查询的条件依赖父查询当前行的数据,需要父查询和子查询“联动”执行。
  • 执行顺序
    1. 父查询先取第一行数据,把相关值(比如学号)传给子查询;
    2. 子查询用这个值计算结果(比如该学号的平均分);
    3. 父查询用结果判断当前行是否符合条件,符合就保留;
    4. 重复步骤1-3,直到处理完所有行。
  • 例子:查每个学生超过自己平均分的课程成绩。
    SELECT Sno, Cno 
    FROM SC x  -- x是外层表的别名
    WHERE Grade >= (SELECT AVG(Grade) FROM SC y  -- y是内层表的别名WHERE y.Sno = x.Sno  -- 内层用x的学号找对应数据
    )
    
    通俗理解
    • 先看第一个学生(比如学号20180001),子查询算出他的平均分是89;
    • 父查询检查他的每门课成绩是否≥89,符合条件的就选出来;
    • 再处理下一个学生,重复这个过程。

3. 经典案例:查每门课的最高分学生

需求:找出每门课成绩最高的学生的学号、课程号和成绩。
SQL写法

SELECT sno, cno, grade 
FROM sc x
WHERE grade = (SELECT MAX(grade) FROM sc yWHERE y.cno = x.cno  -- 内层根据外层的课程号(x.cno)找最高分
)

执行逻辑

  1. 外层先取第一行数据(比如课程81001,学号20180001,成绩85);
  2. 子查询根据课程号81001,算出该课程的最高分是85;
  3. 比较当前成绩85是否等于最高分85,等于就保留;
  4. 处理下一行(比如课程81002,学号20180002,成绩98),子查询算出该课程最高分98,符合条件,保留;
  5. 以此类推,直到所有行处理完。

四、带有ANY(SOME)或ALL谓词的子查询

1. 什么是ANY和ALL?

  • ANY = 任意一个只要满足集合中的某一个值

  • 类似“至少有一个行符合条件”。

  • ALL = 所有必须满足集合中的每一个值

  • 类似“所有行都要符合条件”。

举个生活例子

  • 如果你说“我要选一门 比ANY数学课分数高 的课” → 只要有一门数学课分数比它低就行;
  • 如果你说“我要选一门 比ALL数学课分数高 的课” → 必须所有数学课分数都比它低!

2. ANY/ALL怎么用?

格式
比较运算符 + ANY/ALL + (子查询)
比如:

  • > ANY:大于子查询结果中的某个值
  • < ALL:小于子查询结果中的所有值

常见搭配表

需求用ANY/ALL等价于聚集函数例子
等于集合中某个值= ANYINSno IN (子查询) 等价于 Sno = ANY (子查询)
不等于集合中所有值<> ALLNOT INSno NOT IN (子查询) 等价于 Sno <> ALL (子查询)
大于集合中最小的> ANY> MIN比任意一个值大 → 只要大于最小值就行
大于集合中最大的> ALL> MAX比所有值大 → 必须大于最大值
小于集合中最大的< ANY< MAX比某个值小 → 只要小于最大值就行
小于集合中最小的< ALL< MIN比所有值小 → 必须小于最小值

3. 经典案例

场景:学生表中有计算机专业和其他专业,想找非计算机专业中年龄相关的学生

表结构

SnameSmajorSbirthdate
张三计算机2000-01-01
李四信息安全2001-03-08
王五计算机2000-12-31
赵六数据科学2002-06-12

案例1:查非计算机专业中,比计算机专业任意一个学生年龄小的学生(出生日期更晚)

需求翻译:只要比计算机专业里至少一个人年龄小(即出生日期晚于计算机专业的某个人)。

SELECT Sname, Smajor, Sbirthdate
FROM Student
WHERE Smajor <> '计算机'  -- 排除计算机专业AND Sbirthdate > ANY (SELECT Sbirthdate FROM Student WHERE Smajor = '计算机');

执行逻辑

  • 子查询先找出计算机专业的所有出生日期:2000-01-012000-12-31
  • > ANY 表示只要大于其中任意一个值就行 → 比如赵六的2002-06-12大于2000-01-01,符合条件。

等价写法:用MIN聚集函数

SELECT ...
WHERE Sbirthdate > (SELECT MIN(Sbirthdate) FROM Student WHERE Smajor='计算机');

(因为只要大于最小值,必然大于某个值)

案例2:查非计算机专业中,比计算机专业所有学生年龄都小的学生(出生日期更晚)

需求翻译:必须比计算机专业里所有人年龄都小(出生日期晚于计算机专业的所有人)。

SELECT Sname, Smajor, Sbirthdate
FROM Student
WHERE Smajor <> '计算机'  AND Sbirthdate > ALL (SELECT Sbirthdate FROM Student WHERE Smajor = '计算机');

执行逻辑

  • > ALL 表示必须大于所有值 → 计算机专业最大的出生日期是2000-12-31,只有赵六的2002-06-12大于它,所以只有赵六符合。

等价写法:用MAX聚集函数

SELECT ...
WHERE Sbirthdate > (SELECT MAX(Sbirthdate) FROM Student WHERE Smajor='计算机');

(必须大于最大值,才能大于所有值)

4. ANY/ALL和聚集函数的等价表

需求用ANY/ALL等价的聚集函数写法
大于某个值> ANY> MIN
大于所有值> ALL> MAX
小于某个值< ANY< MAX
小于所有值< ALL< MIN
等于某个值(IN)= ANYIN
不等于所有值(NOT IN)<> ALLNOT IN

五、带有 EXISTS 谓词的子查询

1. EXISTS谓词的基本概念

核心作用:判断子查询是否有结果,返回的是true或者false,并不关注子查询的具体内容。

  • EXISTS后的子查询能查出数据(结果不为空)时,就返回true,此时外层查询的条件得以成立;
  • 若子查询没有结果(结果为空),则返回false,外层查询的条件不成立。

语法格式

WHERE EXISTS (子查询);
WHERE NOT EXISTS (子查询); -- 子查询为空时返回true

特别说明

  • 子查询通常用SELECT *,这是因为我们只关心子查询有没有结果,而不关心具体查出来的内容是什么。

2. 经典案例

案例1:查询选修了81001号课程的学生姓名。

使用EXISTS的写法

SELECT Sname 
FROM Student 
WHERE EXISTS (SELECT * FROM SC WHERE Sno = Student.Sno AND Cno = '81001'
);

执行步骤

  1. 外层查询从Student表中取出一行数据,获取当前学生的学号,比如20180001
  2. 子查询开始执行,查看SC表中是否存在学号为20180001且课程号为81001的记录;
  3. 若存在这样的记录,子查询返回true,那么这个学生的姓名就会被添加到结果中;
  4. 若不存在,子查询返回false,该学生的姓名不会出现在结果里;
  5. 重复上述步骤,直到处理完Student表中的所有行。

案例2:查询没有选修81001号课程的学生姓名。

使用NOT EXISTS的写法

SELECT Sname 
FROM Student 
WHERE NOT EXISTS (SELECT * FROM SC WHERE Sno = Student.Sno AND Cno = '81001'
);

关键逻辑

  • 当子查询没有结果时,NOT EXISTS返回true,意味着这个学生没有选修该课程。

3. EXISTS与IN的对比

这两种方式在很多情况下可以实现相同的查询效果,但也存在一些差异。

场景EXISTSIN
查询选修了课程的学生WHERE EXISTS (SELECT * FROM SC WHERE SC.Sno = Student.Sno)WHERE Sno IN (SELECT Sno FROM SC)
适用情况适合子查询结果集较大的情况,它是通过判断是否存在来筛选的适合子查询结果集较小的情况,它需要先计算出子查询的所有结果

4. 用EXISTS实现全称量词查询

在SQL里没有直接表示全称量词()的操作符,不过可以借助NOT EXISTS来间接实现。

案例:查询选修了全部课程的学生姓名。

需求理解:要找出这样的学生,对于所有课程而言,他们都有选修记录。
SQL写法

SELECT Sname 
FROM Student 
WHERE NOT EXISTS (-- 查找一门课程SELECT * FROM Course WHERE NOT EXISTS (-- 检查该学生是否选修了这门课SELECT * FROM SC WHERE Sno = Student.Sno AND Cno = Course.Cno)
);

逻辑拆解

  • 从外层看,对于每个学生,先假设存在一门他没选修的课程;
  • 中间的子查询负责遍历所有课程;
  • 最内层的子查询去验证该学生是否选修了这门课程;
  • 如果对于某门课程,学生没有选修记录,那么NOT EXISTS就会返回false,这个学生就不符合要求;
  • 只有当所有课程都被学生选修了,也就是对于所有课程,内层子查询都能查到记录,NOT EXISTS才会返回true,这个学生才会被查询出来。

以上就是这篇博客的全部内容,下一篇我们将继续探索更多精彩内容。

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的数据库系统概论专栏
https://blog.csdn.net/2402_83322742/category_12911520.html?spm=1001.2014.3001.5482

非常感谢您的阅读,喜欢的话记得三连哦

在这里插入图片描述

相关文章:

  • Client-Side Path Traversal 漏洞学习笔记
  • 【Java学习笔记】注解
  • 接口安全SOAPOpenAPIRESTful分类特征导入项目联动检测
  • NodeJS全栈开发面试题讲解——P5前端能力(React/Vue + API调用)
  • RabbitMQ-Go 性能分析
  • 【irregular swap】An Examination of Fairness of AI Models for Deepfake Detection
  • Textacy:Python 中的文本数据清理和规范化简介
  • java Map双列集合
  • 【HarmonyOS Next之旅】DevEco Studio使用指南(二十九) -> 开发云数据库
  • Spring MVC参数绑定终极手册:单多参/对象/集合/JSON/文件上传精讲
  • 【Linux】Linux文件系统详解
  • 包管理后续部分
  • Window系统程序加入白名单
  • unix/linux source 命令,在当前的 Shell 会话中读取并执行指定文件中的命令
  • 【GPT入门】第40课 vllm与ollama特性对比,与模型部署
  • Leetcode 3568. Minimum Moves to Clean the Classroom
  • 【云安全】以Aliyun为例聊云厂商服务常见利用手段
  • Java大厂后端技术栈故障排查实战:Spring Boot、Redis、Kafka、JVM典型问题与解决方案
  • Vue3.5 企业级管理系统实战(二十一):菜单权限
  • flask pyinstaller打包exe,出现module not found问题
  • 包小盒设计网站/百度查重免费入口
  • 品牌网站建设有什么作用/自己建网站怎么建
  • 网站建设的网站/互联网推广是什么工作内容
  • 网站不稳定有什么影响/seo托管
  • 五金日杂店做网站/常用的网络推广方式有哪些
  • 线上兼职/seo优化步骤