课程 6.1 · 阅读时间:~7 分钟
SQL 子查询帮助你将一个问题分解为多个步骤,放在一个语句中。在本课程中,你将学习子查询的核心概念、主要类型,以及如何在 SELECT、WHERE 和 FROM 中使用它们,配合 Sakila 示例。
子查询简介:嵌套查询和内联视图
在之前的课程中,你已经学习了如何获取数据并使用 JOIN 组合表。但在实际任务中,你通常需要先计算一个中间结果,然后再在主查询中使用它。
这正是子查询的用途:它们使 SQL 逻辑逐步进行,更易于推理。
什么是子查询
子查询是嵌套在另一个 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_cust 与 payment 连接。
子查询比 JOIN 更方便的情况
- 当你需要首先获取一个中间值时的逐步逻辑。
- 在不使主查询过于复杂的情况下,通过聚合(
AVG、MAX、MIN)进行过滤。 - 对于缺失关系检查,
NOT IN或NOT EXISTS通常是自然选择。
本课程的关键要点:
- 子查询是嵌套在另一个 SQL 查询内部的
SELECT。 - 内部查询通常在外部查询之前运行。
- 子查询可以返回一个值、一组值或一个完整的表状集合。
FROM中的子查询称为内联视图,并需要一个别名。- 子查询有助于编写更清晰、更灵活的 SQL。
常见问题
WHERE 中的子查询和 FROM 中的子查询有什么区别?
WHERE 中的子查询通常用于过滤外部查询的行。FROM 中的子查询构建一个临时数据集(内联视图),你可以进一步连接和处理。
我是否总是需要为 FROM 中的子查询提供别名?
是的。在大多数数据库管理系统中,FROM 中的子查询必须有一个别名。没有它,查询将失败。
面试问题
什么是子查询,它是如何执行的?
子查询是嵌套在外部 SQL 查询内部的 SELECT。通常,内部查询首先执行,外部查询使用其结果进行过滤或构建最终输出。
标量子查询和多行子查询有什么区别?
标量子查询返回一个值,通常与 = 或 > 一起使用。多行子查询返回一组值,并与 IN、ANY 或 ALL 一起使用。
SQL 中的内联视图是什么?
内联视图是 FROM 中的子查询,在一个查询中表现得像一个临时表。它必须有一个别名,以便你可以引用其列。
在下一课中,我们将深入探讨 WHERE 中的子查询,并涵盖运算符 IN、EXISTS、ANY 和 ALL。