子查询入门:嵌套查询与内联视图
第 6.1 课 · 阅读时间:约 7 分钟 · 练习题 ↓
SQL 子查询可以帮你在一条语句中把一个问题拆成多个步骤。在本课中,你将学习子查询的核心思想、主要类型,以及如何通过 Sakila 示例在 SELECT、WHERE 和 FROM 中使用它们。
在前面的课程中,你已经学会了查询数据以及用 JOIN 连接表。但在实际任务中,你经常需要先计算一个中间结果,然后在主查询中使用它。
这正是子查询的用途:它让 SQL 逻辑分步进行,更容易理解。
什么是子查询
子查询是嵌套在另一条 SQL 查询中的 SELECT 语句。包含它的查询称为外层查询。
子查询总是写在括号 () 中。
子查询如何执行
在大多数情况下,DBMS 会先执行内层查询,然后把它的结果交给外层查询,由外层查询完成过滤或构建最终结果集。
-- Conceptual example
SELECT column_name
FROM table_name
WHERE column_name = (SELECT value FROM another_table);
说明:先计算括号中的表达式,再应用外层的 WHERE 条件。
子查询的主要类型
- 标量子查询:返回一个值(一行一列)。
- 多行子查询:返回一组值(一列多行)。
- 表子查询(内联视图):返回由行和列组成的集合,作为临时表使用。
SELECT 中的子查询
当你需要在基础列旁边增加一个额外指标、同时又不改变行的粒度时,可以把子查询直接放在 SELECT 列表中。当 JOIN 容易导致行重复或需要繁重的聚合时,这种写法特别有用。
场景 1: 显示每位客户最近一次付款(日期和金额)。
SELECT
c.customer_id,
c.first_name,
c.last_name,
(
SELECT p.payment_date
FROM payment AS p
WHERE p.customer_id = c.customer_id
ORDER BY p.payment_date DESC
LIMIT 1
) AS last_payment_date,
(
SELECT p.amount
FROM payment AS p
WHERE p.customer_id = c.customer_id
ORDER BY p.payment_date DESC
LIMIT 1
) AS last_payment_amount
FROM
customer AS c
LIMIT 10;
结果:每位客户恰好得到一条“最近”的付款。用 JOIN 实现通常更复杂:要先算出最大日期,再重新连接并处理日期相同的情况。
场景 2: 显示每笔付款及其与该客户平均付款额的偏差。
SELECT
p.payment_id,
p.customer_id,
p.amount,
(
SELECT AVG(p2.amount)
FROM payment AS p2
WHERE p2.customer_id = p.customer_id
) AS customer_avg_amount,
p.amount - (
SELECT AVG(p3.amount)
FROM payment AS p3
WHERE p3.customer_id = p.customer_id
) AS delta_from_customer_avg
FROM
payment AS p
LIMIT 15;
结果:每条付款记录保持原有粒度,并得到按客户计算的参照值。用 JOIN 实现则需要单独的聚合表和额外的连接。
WHERE 中的子查询
最常见的情况是在 WHERE 中使用子查询,此时过滤条件依赖于动态计算出的值。
场景: 查找 replacement_cost 高于所有电影平均值的电影。
SELECT
title,
replacement_cost
FROM
film
WHERE
replacement_cost > (
SELECT AVG(replacement_cost)
FROM film
);
结果:内层查询计算平均值,外层查询返回高于该平均值的电影。
FROM 中的子查询(内联视图)
当子查询放在 FROM 中时,它在当前查询中就像一张临时表。这种模式称为内联视图(Inline View)。
重要:内联视图必须有别名。
场景: 获取活跃客户及其付款。
SELECT
active_cust.first_name,
p.amount
FROM
(
SELECT
customer_id,
first_name
FROM
customer
WHERE
active = 1
) AS active_cust
INNER JOIN
payment AS p ON active_cust.customer_id = p.customer_id;
结果:外层查询把子查询结果 active_cust 与 payment 连接起来。
什么时候子查询比 JOIN 更方便
- 需要先得到一个中间值、按步骤处理逻辑时。
- 需要按聚合值(
AVG、MAX、MIN)过滤、又不想让主查询过于复杂时。 - 检查缺失的关联时,
NOT IN或NOT EXISTS往往是自然的选择。
本课要点:
- 子查询是嵌套在另一条 SQL 查询中的
SELECT。 - 内层查询通常在外层查询之前执行。
- 子查询可以返回一个值、一组值,或者一个完整的类表集合。
FROM中的子查询称为内联视图,必须有别名。- 子查询有助于写出更清晰、更灵活的 SQL。
常见问题
WHERE 中的子查询和 FROM 中的子查询有什么区别?
WHERE 中的子查询通常用于过滤外层查询的行。FROM 中的子查询会构建一个临时数据集(内联视图),你可以对它进行连接和进一步处理。
FROM 中的子查询总是需要别名吗?
是的。在大多数 DBMS 中,FROM 中的子查询必须有别名,否则查询会报错。
面试问题
什么是子查询,它是如何执行的?
子查询是嵌套在外层 SQL 查询中的 SELECT。通常内层查询先执行,外层查询利用它的结果进行过滤或构建最终输出。
标量子查询和多行子查询有什么区别?
标量子查询返回一个值,通常与 = 或 > 一起使用。多行子查询返回一组值,与 IN、ANY 或 ALL 一起使用。
SQL 中的内联视图是什么?
内联视图是位于 FROM 中的子查询,在一条查询内部表现得像一张临时表。它必须有别名,这样才能引用它的列。