4.3 子查询


4.3 子查询

本节摘要:子查询是嵌在另一个查询里的查询。本节讲标量子查询、列子查询、表子查询(派生表)、相关子查询几种类型和用法。

阅读收获

阅读完本节,你应当能够:

  1. 区分几种子查询类型
  2. 用子查询在 WHERE/SELECT/FROM 中
  3. 理解相关子查询的执行
  4. 知道何时用 JOIN 代替子查询

一、什么是子查询

子查询是嵌套在另一个 SQL 语句里的 SELECT。外层查询用子查询的结果作为条件、列或表。子查询用括号括起来。

SELECT * FROM Orders WHERE Amount > (SELECT AVG(Amount) FROM Orders); -- 比平均金额大的订单

二、几种子查询类型

图 4-3 子查询类型

图 4-3 子查询类型

三、标量子查询

返回单个值,用在比较或 SELECT 列里:

-- 比平均大的订单 SELECT * FROM Orders WHERE Amount > (SELECT AVG(Amount) FROM Orders); -- 每个客户及其订单数(标量子查询当列) SELECT FirstName, (SELECT COUNT(*) FROM Orders WHERE Orders.CustomerID = Customers.CustomerID) AS 订单数 FROM Customers;

四、列子查询

返回一列多行,配合 IN/ANY/ALL:

-- 有订单的客户 SELECT * FROM Customers WHERE CustomerID IN (SELECT CustomerID FROM Orders); -- 金额比所有北京客户都大(ALL) SELECT * FROM Orders WHERE Amount > ALL (SELECT Amount FROM Orders o JOIN Customers c ON o.CustomerID = c.CustomerID WHERE c.City = '北京');

五、表子查询(派生表)

返回多列多行,用在 FROM 后当临时表,必须起别名:

SELECT t.City, t.总数 FROM ( SELECT City, COUNT(*) AS 总数 FROM Customers GROUP BY City ) AS t WHERE t.总数 > 5;

六、相关子查询

子查询引用外层查询的列,外层每行都执行一次子查询。EXISTS 是典型:

-- 有订单的客户 SELECT * FROM Customers c WHERE EXISTS (SELECT 1 FROM Orders WHERE Orders.CustomerID = c.CustomerID);

EXISTS 只判断有没有行返回,不关心具体值,存在性检查很高效。

七、子查询 vs JOIN

很多子查询能改写成 JOIN,JOIN 通常更直观且优化器友好:

-- 子查询写法 SELECT * FROM Customers WHERE CustomerID IN (SELECT CustomerID FROM Orders); -- JOIN 写法(等价,常更优) SELECT DISTINCT c.* FROM Customers c JOIN Orders o ON c.CustomerID = o.CustomerID;

⚠️ 常见坑:相关子查询在大表上每行执行一次,可能很慢。能用 JOIN 改写就改写,或确保关联列有索引。

💡 关键直觉:子查询用于"算一个值/列当条件",JOIN 用于"连表展示列"。能用 JOIN 表达的优先 JOIN,子查询适合标量条件和 EXISTS 存在性判断。

要点串联

  • 标量子查询:返回单值,用于比较/SELECT 列。
  • 列子查询:返回一列,配合 IN/ANY/ALL。
  • 表子查询(派生表):返回多列多行,用在 FROM,必须起别名。
  • 相关子查询:引用外层,每行执行一次,EXISTS 判断存在性高效。
  • vs JOIN:能 JOIN 表达的优先 JOIN,子查询适合标量条件和 EXISTS。

下一节讲集合操作——把多个查询结果合并。

常见疑问

Q1:子查询和 JOIN 什么时候互换?

凡是子查询的结果能"平铺"成表来连,都可以改写为 JOIN。比如 WHERE CustomerID IN (SELECT CustomerID FROM ...) 可改写为 JOIN ...。选择原则:JOIN 语义更清晰、性能通常更好;子查询在"只取一个值(标量)"和"存在性判断(EXISTS)"时更自然。面试常考改写,建议同一需求两种都写一遍。

Q2:相关子查询为什么慢?

相关子查询每处理外层一行就执行一次内层查询,行数多时就是"逐行跑查询",性能灾难。优化方向:改写为 JOIN、给内层关联列加索引、或先用子查询一次性算出结果集再关联(非相关)。

Q3:EXISTS 和 IN 到底谁快?

取决于场景。EXISTS 是"找到就停"(适合存在性判断),IN 会把子查询结果全部物化再比较。子查询结果很大时 EXISTS 通常更快;子查询结果小而外层表大时 IN 可能更优。原则:存在性判断优先 EXISTS,别盲目用 IN。两者语义在 NULL 处理上也有差异(NOT IN 遇 NULL 返回空)。

Q4:子查询能用在 SELECT 列里吗?

能,标量子查询可以放在 SELECT 列表(如"每个客户对应的订单数")。但要小心:标量子查询必须返回且只返回一行一列,否则报错。这类"列子查询"常用但别滥用,逻辑复杂时用 JOIN 分组代替更清晰。

Q5:子查询可以嵌套几层?

语法上无限制,但嵌套过深(大于 3 层)可读性和性能都崩。建议用派生表(FROM 里的子查询)拆分,或把复杂逻辑拆成多个简单查询。可读性优先于"一行写完"。

动手做一做

子查询的类型多、用法活,建议通过改写练习来加深理解。

准备两张练习表,比如客户表和订单表。

第一个练习:写一条查询,找出金额大于平均金额的订单。这里子查询返回单个值,放在比较条件里,是标量子查询的典型用法。

第二个练习:找出下过订单的客户。可以写一个返回一列的子查询配合存在性判断,也可以改写为多表连接,把两种写法都写出来对比,体会它们的等价关系。

第三个练习:按城市统计客户数,再筛出客户数超过三人的城市。用派生表把统计结果包一层,体会子查询充当临时表的用法。

第四个练习:找出下过订单的客户,用关联子查询和存在性判断各写一遍,观察关联子查询和外层查询之间的引用关系。

这四个练习覆盖了标量、列、表、相关四类子查询,并且每个练习都给出了与连接或派生表互相改写的机会。同一需求两种写法,会让你真正理解"什么时候用子查询、什么时候用连接"。

一句话记忆

子查询是嵌在查询里的查询,按返回内容分为单值、一列、整表三类,引用外层列的就叫相关子查询。能和连接互相改写,能判断存在性的用存在性判断,这两点记住就够用了。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U