文章目录
- 前言
- [一、子查询(Subquery) VS 连接(Join)](#一、子查询(Subquery) VS 连接(Join))
-
- 1.1子查询与连接的比较
- [1.2 什么时候用子查询](#1.2 什么时候用子查询)
- [1.3 小结](#1.3 小结)
- 二、常见的子查询
-
- [2.1 标量子查询(Scalar Subquery)](#2.1 标量子查询(Scalar Subquery))
- [2.2 列子查询(Column Subquery)](#2.2 列子查询(Column Subquery))
- [2.3 表子查询(Table Subquery)](#2.3 表子查询(Table Subquery))
- [三、相关子查询(Correlated Subquery)与非相关子查询(Non-Correlated Subquery)](#三、相关子查询(Correlated Subquery)与非相关子查询(Non-Correlated Subquery))
-
- [3.1 非相关子查询](#3.1 非相关子查询)
- [3.2 相关子查询](#3.2 相关子查询)
- 总结
前言
子查询是一个嵌套在 SELECT、INSERT、UPDATE 或 DELETE 语句或其他子查询中的查询。将一个查询的结果作为另一个查询的条件或数据源,实现对数据的多层筛选和处理。
子查询也称为内部查询或内部选择,而包含子查询的语句也称为外部查询或外部选择。
一、子查询(Subquery) VS 连接(Join)
1.1子查询与连接的比较
子查询常用于解决一些无法通过单一查询直接完成的复杂数据检索任务。例如,当需要获取与特定条件相关联的数据,或者从一个表中获取满足另一个表中某些条件的数据时,子查询就能够发挥重要作用。它可以替代一些复杂的连接操作,在某些情况下使查询逻辑更加清晰,也能帮助实现数据的分层分析和过滤。所以,其实许多包含子查询的Transact-SQL语句也可以通过联接来表示,并且在某些必须检查存在性的情况下,连接会产生更好的性能。
本文实例ER 图
下面分别通过获取学生班级的T-SQL比较下子查询和连接实现起来异同
子查询
sql
select
T_Student.Name,
T_Student.Age,
(SELECT ClassName FROM T_Class WHERE Id = T_Student.ClassId) AS ClassName
from T_Student
where T_Student.ClassId in (
select T_Class.Id from T_Class
where T_Class.ClassName = '计算机科学与技术2024级1班'
)
连接
sql
select
T_Student.Name,
T_Student.Age,
T_Class.ClassName
from T_Student
inner join T_Class on T_Class.Id = T_Student.ClassId
where T_Class.ClassName = '计算机科学与技术2024级1班'
执行结果
通过观察执行结果,两种写法在功能上完全等价。但是对于子查询而言,先执行子查询获取班级 ID,再筛选学生,最终在显示班级名称的时候又用到了关联子查询查询班级名称。在某些情况下,子查询可能导致多次查询,而连接查询可通过一次扫描完成,因此效率可能更高。但实际性能需结合执行计划判断。
1.2 什么时候用子查询
子查询可能比连接查询更具优势,主要体现在以下几个方面
- 逻辑分层更清晰,可读性更强
- 处理存在性检查更简洁
- 避免多表连接的冗余数据
- 支持更灵活的标量计算
举个例子,我们查询学生课程里,每门课程绩在80分以上的同学。分别于子查询和连接。
子查询(EXISTS)写法
sql
SELECT
T_Student.Name
FROM T_Student
WHERE EXISTS (
SELECT 1
FROM T_StudentCourse
WHERE T_StudentCourse.StudentId = T_Student.Id
AND T_StudentCourse.Score >80
);
连接
sql
select
T_Student.Name
from T_Student
inner join T_StudentCourse on T_StudentCourse.StudentId = T_Student.Id
where T_StudentCourse.Score >80

执行结果里我们发现使用连接语法,结果出现重复。这是因为inner join多表连接的冗余数据,一个学生是存在多门课程大于80分的情况。若用 JOIN 实现,需额外处理去重(避免学生因多条未完成作业重复出现),当只需要关联表的某个字段(而非所有字段)时,子查询可以避免 JOIN 带来的全表字段冗余。
1.3 小结
特性 | 连接查询 | 子查询实现 |
---|---|---|
数据关联方式 | 通过 JOIN 直接合并表 | 通过子查询嵌套获取关联数据 |
执行顺序 | 先关联表,再过滤结果 | 先执行子查询获取班级 ID,再筛选学生 |
适用场景 | 多表数据展示场景 | 需分步计算或条件依赖场景 |
性能 通常更优 | (数据库可优化连接) | 可能多次执行子查询(依赖数据库优化) |
- 可读性:原连接查询更简洁直观,子查询版本在复杂场景下可能降低可读性。
- 等价性:两种写法在功能上完全等价,但执行计划可能不同。
子查询更适合逻辑分层明确、存在性检查、标量值计算等场景,优势在于逻辑清晰、代码简洁;而连接查询(JOIN)更适合需要多表字段关联展示的场景。实际开发中需根据具体需求(如可读性、性能、数据量)选择更合适的方式。
二、常见的子查询
2.1 标量子查询(Scalar Subquery)
标量子查询返回的是单个值,可以作为查询语句中的一个常量使用,通常用于替换主查询中需要单值的位置。
比如我们查找每个学生选修的课程数量。
sql
SELECT
T_Student.Name,
(SELECT COUNT(*) FROM T_StudentCourse WHERE Id = T_StudentCourse.StudentId) AS CourseCount
FROM T_Student
查询课程数据量的子查询语句返回课程数量这个常量,作为查询结果集中的一个列。
2.2 列子查询(Column Subquery)
列子查询返回一列多行,可以和主查询的结果进行比较或者连接,通常与 IN、NOT IN等运算符配合使用
比如我们查找 计算机科学与技术2024级1班 和 软件工程2024级2班 里的学生。
sql
select
T_Student.Name,
T_Student.Age
from T_Student
where T_Student.ClassId in (
select T_Class.Id from T_Class
where T_Class.ClassName = '计算机科学与技术2024级1班'
or T_Class.ClassName = '软件工程2024级2班'
)
查询两个班级这个列子查询返回的是多行单列值,然后在通过IN查询的一部分,获取出这两个班级的学生。
2.3 表子查询(Table Subquery)
表子查询返回的是一个表或视图,可以嵌套在另一个查询语句中使用
比如我们查找学人工智能导论里成绩大于80的学生。
sql
select *
from(select * from T_StudentCourse
where T_StudentCourse.CourseId =
(select Id from T_Course where CourseName = '人工智能导论'))t
wheret.Score > 80;
此处用到了两个子查询,第一个标量子查询返回课程的Id,然后第二个表子查询返回所有匹配的数据,通过一个临时表返回。最后通过成绩大于80的where判断返回结果。
标量子查询,列子查询,表子查询的分类依据是根据放回的结果来判断。标量子查询返回的是一列一列(单个值),列子查询返回一列多行,表子查询返回的是多列多行(一个表或视图)。
三、相关子查询(Correlated Subquery)与非相关子查询(Non-Correlated Subquery)
相关子查询和 非相关子查询 是根据子查询与主查询的依赖关系划分的两类核心子查询类型。二者通过子查询是否依赖主查询来区分。
维度 | 非相关子查询 | 相关子查询 |
---|---|---|
依赖关系 | 子查询不依赖主查询的列(独立执行) | 子查询依赖主查询的列(逐行关联) |
执行次数 | 仅执行一次 | 主查询每一行触发一次子查询 |
典型场景 | 全局比较 | 局部比较 |
3.1 非相关子查询
非相关子查询,其子查询独立计算,仅执行一次。
下面我们查询年龄大于全体学生平均年龄的学生姓名、年龄及班级名称
sql
SELECT
T_Student.Name,
T_Student.Age,
T_Class.ClassName
FROM T_Student
INNER JOIN T_Class ON T_Student.ClassId = T_Class.Id
WHERE T_Student.Age > (
-- 子查询:计算全体学生的平均年龄(独立执行一次)
SELECT AVG(Age)
FROM T_Student);
- 先执行子查询 SELECT AVG(Age) FROM T_Student,得到全体学生的平均年龄(如 18 岁)。
- 主查询通过 INNER JOIN 关联学生表和班级表,筛选出年龄大于 18 岁的学生,并返回姓名、年龄和班级名称。
3.2 相关子查询
子查询依赖主查询,主查询每处理一行,子查询重新计算一次
下面我们查询年龄大于所在班级平均年龄的学生姓名、年龄及班级名称。
sql
-- 非相关子查询示例(子查询不依赖主查询的列)
SELECT
T_Student.Name,
T_Student.Age,
T_Class.ClassName
FROM T_Student
INNER JOIN T_Class ON T_Student.ClassId = T_Class.Id
WHERE T_Student.Age > (
-- 子查询:计算当前行班级的平均年龄(依赖主查询的 ClassId)
SELECT AVG(Age)
FROM T_Student s
WHERE s.ClassId = T_Student.ClassId -- 关键:关联主查询的当前行班级 ID
);
- 子查询根据当前行的 T_Student.ClassId ,计算该的所有学生的平均年龄。
- 比较学生年龄是否该班级平均年龄,若满足则保留该行。
- 主查询处理下一行,子查询重新计算下一个班级的平均年龄,通过班级Id重复比较。
总结
以上就是 SQL 中子查询的核心概念,对比了子查询与连接的差异,详述了标量子查询、列子查询、表子查询的类型特点,剖析了相关子查询(依赖主查询逐行执行)与非相关子查询(独立执行一次)的区别。