子查询与 CTE

SQL 笔试与手撕题教程

写法建议
面试手撕 SQL 时,强烈建议用 CTE 而不是嵌套子查询。CTE 分层写,面试官能一眼看懂你的思路;嵌套三四层的子查询很容易把自己和面试官都绕晕。

CTE(Common Table Expression)

CTE 用 WITH 定义临时结果集,让复杂查询的逻辑分层更清晰。你可以把它理解为"给子查询起个名字,放在前面"。

WITH first_order AS (
    SELECT user_id, MIN(order_time) AS first_time
    FROM orders
    GROUP BY user_id
)
SELECT f.user_id, f.first_time, o.amount
FROM first_order f
JOIN orders o
    ON f.user_id = o.user_id
    AND f.first_time = o.order_time;

CTE vs 嵌套子查询

同样的逻辑用嵌套子查询写:

SELECT f.user_id, f.first_time, o.amount
FROM (
    SELECT user_id, MIN(order_time) AS first_time
    FROM orders
    GROUP BY user_id
) f
JOIN orders o
    ON f.user_id = o.user_id
    AND f.first_time = o.order_time;

两种写法语义完全等价,但 CTE 的优势在于:

链式 CTE

WITH
step1 AS (
    SELECT user_id, MIN(order_time) AS first_time
    FROM orders GROUP BY user_id
),
step2 AS (
    SELECT s.user_id, s.first_time, o.amount AS first_amount
    FROM step1 s
    JOIN orders o ON s.user_id = o.user_id AND s.first_time = o.order_time
),
step3 AS (
    SELECT user_id, AVG(amount) AS avg_amount
    FROM orders GROUP BY user_id
)
SELECT s2.user_id, s2.first_amount, s3.avg_amount
FROM step2 s2
JOIN step3 s3 ON s2.user_id = s3.user_id
WHERE s2.first_amount > s3.avg_amount;

每一步都有明确的语义命名,比嵌套三四层的子查询清晰得多。面试中写 CTE 也更容易让面试官看懂你的思路。

练习

练习

首单金额高于均值的用户

给定 orders 表,找出每个用户的首单金额,筛选出首单金额高于该用户所有订单平均金额的用户。输出 user_id、首单金额和平均金额。

提示:分两个 CTE:一个求首单,一个求平均,最后 JOIN 比较

SQL 编辑器

点击「运行」查看查询结果

查看答案
参考答案
WITH first_ord AS (
  SELECT user_id, MIN(order_time) AS first_time
  FROM orders GROUP BY user_id
),
first_amount AS (
  SELECT f.user_id, o.amount AS first_amt
  FROM first_ord f
  JOIN orders o ON f.user_id = o.user_id AND f.first_time = o.order_time
),
avg_amount AS (
  SELECT user_id, ROUND(AVG(amount), 1) AS avg_amt
  FROM orders GROUP BY user_id
)
SELECT fa.user_id, fa.first_amt, aa.avg_amt
FROM first_amount fa
JOIN avg_amount aa ON fa.user_id = aa.user_id
WHERE fa.first_amt > aa.avg_amt;
解析

三步链式 CTE:先找首单时间,再 JOIN 回去拿首单金额,同时算平均金额,最后比较。用户 1 首单 500 > 均值 333,用户 3 首单 1000 > 均值 600,都会被选出来。