⏳ 十月只剩最后几天!SQLtest.online 依靠捐款免费运营,我们仍需达成本月目标,才能让网站在十一月继续运行。每一份捐款都很重要。 立即支持 →
SQL 代码已复制到剪贴板

子查询入门:嵌套查询与内联视图

第 6.1 课 · 阅读时间:约 7 分钟 · 练习题 ↓

SQL 子查询可以帮你在一条语句中把一个问题拆成多个步骤。在本课中,你将学习子查询的核心思想、主要类型,以及如何通过 Sakila 示例在 SELECT、WHERE 和 FROM 中使用它们。

在前面的课程中,你已经学会了查询数据以及用 JOIN 连接表。但在实际任务中,你经常需要先计算一个中间结果,然后在主查询中使用它。

这正是子查询的用途:它让 SQL 逻辑分步进行,更容易理解。

SQL 子查询示意图:先执行内层查询,再执行外层查询,以及在 SELECT、WHERE 和 FROM 中的用法

什么是子查询

子查询是嵌套在另一条 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 中的子查询,在一条查询内部表现得像一张临时表。它必须有别名,这样才能引用它的列。

尝试解决以下任务,以巩固您在本课中学到的内容。

  1. 什么是子查询?
  2. 最高替换成本电影
  3. 比 HENRY BERRY 更受欢迎的演员
  4. 租赁数量较高的客户
  5. 查找满足所有票价条件的飞机