🙏 感谢您的支持! 我们在七月份已经筹集了 $65 — 这足够我们工作到下个月。请帮助我们保持进度,进一步支持这个项目。 支持这个项目 →
SQL 代码已复制到剪贴板

课程 6.1 · 阅读时间:~7 分钟

SQL 子查询帮助你将一个问题分解为多个步骤,放在一个语句中。在本课程中,你将学习子查询的核心概念、主要类型,以及如何在 SELECTWHEREFROM 中使用它们,配合 Sakila 示例。

子查询简介:嵌套查询和内联视图

在之前的课程中,你已经学习了如何获取数据并使用 JOIN 组合表。但在实际任务中,你通常需要先计算一个中间结果,然后再在主查询中使用它。

这正是子查询的用途:它们使 SQL 逻辑逐步进行,更易于推理。

SQL 子查询图示,显示内部查询先执行,然后是外部查询,以及在 SELECT、WHERE 和 FROM 中的使用

什么是子查询

子查询是嵌套在另一个 SQL 查询内部的 SELECT 语句。包含它的查询称为 外部查询

子查询总是用括号 () 包裹。

子查询的执行方式

在大多数情况下,数据库管理系统(DBMS)首先执行内部查询。然后将其结果传递给外部查询,外部查询完成过滤或构建最终结果集。

-- 概念示例
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 中时,它在当前查询中表现得像一个临时表。这个模式称为 内联视图

重要提示:内联视图必须有一个别名。

场景: 获取活跃客户及其付款。

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_custpayment 连接。


子查询比 JOIN 更方便的情况

  • 当你需要首先获取一个中间值时的逐步逻辑。
  • 在不使主查询过于复杂的情况下,通过聚合(AVGMAXMIN)进行过滤。
  • 对于缺失关系检查,NOT INNOT EXISTS 通常是自然选择。

本课程的关键要点:

  • 子查询是嵌套在另一个 SQL 查询内部的 SELECT
  • 内部查询通常在外部查询之前运行。
  • 子查询可以返回一个值、一组值或一个完整的表状集合。
  • FROM 中的子查询称为内联视图,并需要一个别名。
  • 子查询有助于编写更清晰、更灵活的 SQL。

常见问题

WHERE 中的子查询和 FROM 中的子查询有什么区别?

WHERE 中的子查询通常用于过滤外部查询的行。FROM 中的子查询构建一个临时数据集(内联视图),你可以进一步连接和处理。

我是否总是需要为 FROM 中的子查询提供别名?

是的。在大多数数据库管理系统中,FROM 中的子查询必须有一个别名。没有它,查询将失败。

面试问题

什么是子查询,它是如何执行的?

子查询是嵌套在外部 SQL 查询内部的 SELECT。通常,内部查询首先执行,外部查询使用其结果进行过滤或构建最终输出。

标量子查询和多行子查询有什么区别?

标量子查询返回一个值,通常与 => 一起使用。多行子查询返回一组值,并与 INANYALL 一起使用。

SQL 中的内联视图是什么?

内联视图FROM 中的子查询,在一个查询中表现得像一个临时表。它必须有一个别名,以便你可以引用其列。

在下一课中,我们将深入探讨 WHERE 中的子查询,并涵盖运算符 INEXISTSANYALL

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

  1. 什么是子查询?
  2. 最高替换成本电影
  3. 共享影片的客户