0Pricing
Coding Interview Prep · 课时

将嵌套查询重构为 CTE

掌握现场面试中的常见模式:将难以阅读的嵌套查询转换为逐步执行的 CTE

将嵌套查询重构为 CTE 是 CoddyKit 上的免费 Coding Interview Prep 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Coding Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Coding Interview Prep 课程共包含 4 节课。

现场面试中的重构

这是一个常见的中级面试题:这里有一条查询,请让它更易读。面试官会给您一条深度嵌套的 SELECT,然后观察您如何拆解它。将嵌套转换为一系列命名 CTE,是最清晰的答案。

本课将逐步讲解确切的操作,让您能够在白板上从容完成。

从最内层查询开始

嵌套子查询在概念上是从内到外执行的。因此,阅读查询时也要从内到外:先找到最深处位于括号内的 SELECT;这就是流水线的第一个阶段。

为它起一个描述性名称,并将它提取到 CTE 中。之前引用该内部代码块的所有地方,现在都改为引用 CTE 名称。

SELECT *
FROM (
    SELECT customer_id, SUM(amount) AS total
    FROM orders
    GROUP BY customer_id
) t
WHERE t.total > 1000;

向上提取一层到 CTE 中

将最内层的派生表提取并提升为一个 CTE。外层查询保持不变,只是现在改为从命名的 CTE 中进行查询。

这一步就能减少一层需要在脑中处理的嵌套,并为该步骤提供一个有意义的名称。

WITH spend AS (
    SELECT customer_id, SUM(amount) AS total
    FROM orders
    GROUP BY customer_id
)
SELECT *
FROM spend
WHERE total > 1000;

一个真正的嵌套示例

下面是一个更难重构的例子:两层嵌套,再加上类似相关子查询的筛选条件。目标是求消费最高层级客户的平均订单金额。

这个查询是正确的,但很难阅读。我们将逐个阶段把它拆开。

SELECT AVG(o.amount) AS avg_order
FROM orders o
WHERE o.customer_id IN (
    SELECT customer_id
    FROM (
        SELECT customer_id, SUM(amount) AS total
        FROM orders
        GROUP BY customer_id
    ) s
    WHERE s.total > 1000
);

命名第一个阶段

最深处的代码块计算每位客户的总消费金额。将它提取到名为 spend 的 CTE 中。现在,中间层只需筛选该 CTE。

请注意,每提取一次,嵌套深度就会减少一层,同时增加一个能够自我说明的名称。

WITH spend AS (
    SELECT customer_id, SUM(amount) AS total
    FROM orders
    GROUP BY customer_id
)
SELECT AVG(o.amount) AS avg_order
FROM orders o
WHERE o.customer_id IN (
    SELECT customer_id FROM spend WHERE total > 1000
);

命名第二个阶段

将针对 spend 的筛选条件提取到独立的 CTE big_spenders 中。剩余的主查询就变成针对一个名称清晰的集合进行扁平连接或成员检查。

现在每个阶段都只负责一项工作,这正是整洁 SQL 的标志。

WITH spend AS (
    SELECT customer_id, SUM(amount) AS total
    FROM orders GROUP BY customer_id
),
big_spenders AS (
    SELECT customer_id FROM spend WHERE total > 1000
)
SELECT AVG(o.amount) AS avg_order
FROM orders o
JOIN big_spenders b ON b.customer_id = o.customer_id;

重构时保持语义不变

黄金法则是:重构不得改变结果。请留意那些会悄悄改变输出的陷阱:

  • 将 IN 改为 JOIN,如果右侧不是去重结果,可能会引入重复行。
  • 带有 NULL 的 NOT IN 与 NOT EXISTS 的行为不同。
  • 聚合粒度必须保持不变。

大声说出这些风险,以体现您的严谨性。

验证重构

如何证明重构保持了原有语义?请说明您会运行两个版本并比较行数和校验和,或者在抽样数据上对结果集执行差异比较。

在面试中,即使只是说明我会通过比较计数和几行抽样记录来验证,也能体现出超越单纯改写语法的工程严谨性。

SELECT COUNT(*), SUM(amount)
FROM orders
WHERE customer_id IN (SELECT customer_id FROM big_spenders);

何时 NOT 重构

重构并不总能带来改进。一个层次较浅的子查询单独保留可能更清晰,而过度拆分成许多很小的 CTE 也可能损害可读性。

请根据实际情况判断:当嵌套结构掩盖了意图,或某段逻辑会被重复使用时再进行重构。您可以告诉面试官,当查询从上到下读起来已经是清晰、独立且有名称的步骤时,您就会停止重构。

重构检查清单

一种可以反复复述的方法:

  • 从内到外阅读,找出最深层的子查询。
  • 将它提取为一个命名的 CTE。
  • 向上重复此过程,每次处理一层。
  • 根据每个阶段产生的内容为其命名。
  • 确认结果没有变化(注意 IN/JOIN 和 NULL 陷阱)。

这样就能把令人畏惧的嵌套查询变成平稳、分步骤的重写过程。

讲解您的重构

请边操作边说明:最内层代码块表示每位客户的支出,所以我会将其命名为支出。下一层筛选高额支出者。然后最外层查询计算他们的订单金额平均值。

面试官对沟通能力的重视程度不亚于正确性。按阶段讲解重构过程,能够准确展现他们所期待的中级工程成熟度。

快速检查

请指出将深度嵌套的查询重构为 CTE 时,正确的第一步是什么。

回顾:重构为 CTE

您已经学会了一套平稳、可重复的重构方法:从内到外阅读,将最深层的子查询提取为命名的 CTE,然后每次向外处理一层。

  • 根据每个阶段产生的内容为其命名。
  • 保持语义不变;注意 IN 与 JOIN 导致的重复项以及 NULL 陷阱。
  • 通过比较行数和抽样行来验证结果。
  • 不要过度拆分;当查询已经读起来像清晰的命名步骤时就停止。

CTE 课程到此完成;现在您可以在现场面试中自信地进行重构了。

常见问题解答

「将嵌套查询重构为 CTE」课时是免费的吗?

是的 — 「将嵌套查询重构为 CTE」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Coding Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 Coding Interview Prep 课程共包含 4 节课。

「将嵌套查询重构为 CTE」这节课中我会学到什么?

掌握现场面试中的常见模式:将难以阅读的嵌套查询转换为逐步执行的 CTE 你通过在浏览器中直接运行的动手代码来练习 Coding Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Coding Interview Prep 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Coding Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「将嵌套查询重构为 CTE」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Coding Interview Prep 课中编写并运行代码吗?

能。每节 Coding Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 编写第一个 CTE
  2. 串联多个 CTE
  3. CTE、子查询与临时表
  4. 将嵌套查询重构为 CTE
← 返回 Coding Interview Prep