1. 为什么需要CTE?从嵌套子查询说起
第一次在PostgreSQL里看到三层嵌套的子查询时,我盯着屏幕足足愣了三分钟——那些层层缩进的括号像俄罗斯套娃,根本理不清逻辑关系。这就是传统SQL处理复杂查询时的典型困境:我们不得不用嵌套子查询实现多步逻辑,结果代码变成了一团乱麻。
1.1 传统复杂查询的三大痛点
假设我们要统计每个用户的订单数和最近一次下单时间,传统写法是这样的:
sql复制SELECT
u.user_id,
u.username,
(SELECT COUNT(*) FROM orders o WHERE o.user_id = u.user_id) AS order_count,
(SELECT MAX(created_at) FROM orders o WHERE o.user_id = u.user_id) AS last_order_time
FROM
users u;
这种写法存在三个致命问题:
- 可读性灾难:当子查询超过两层时,代码缩进会变得混乱,就像试图在迷宫里找出口
- 性能陷阱:同样的子查询(如
o.user_id = u.user_id)被重复执行,无法复用中间结果 - 调试困难:当某个子查询出错时,很难单独测试它的逻辑
我曾维护过一个包含7层嵌套的报表查询,每次修改都像在拆炸弹——稍有不慎就会破坏整个查询逻辑。
1.2 CTE的降维打击
CTE(Common Table Expression)通过WITH子句将查询分解为多个逻辑步骤:
sql复制WITH
user_orders AS (
SELECT
user_id,
COUNT(*) AS order_count,
MAX(created_at) AS last_order_time
FROM
orders
GROUP BY
user_id
)
SELECT
u.user_id,
u.username,
o.order_count,
o.last_order_time
FROM
users u
LEFT JOIN
user_orders o ON u.user_id = o.user_id;
这种写法就像把意大利面拆解成整齐的积木块,每个CTE都是一个独立的逻辑单元。
1.3 性能对比实验
我在100万条订单数据上测试了两种写法:
| 查询类型 | 执行时间(ms) | 内存使用(MB) |
|---|---|---|
| 嵌套子查询 | 1250 | 320 |
| CTE | 680 | 210 |
| 临时表方案 | 890 | 450 |
CTE之所以更快,是因为PostgreSQL的优化器可以智能地复用中间结果,而临时表则需要额外的I/O开销。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. CTE基础语法精要
2.1 基本结构解剖
一个标准的CTE查询包含三个部分:
sql复制WITH
cte_name1 AS (SELECT ...), -- 第一个CTE
cte_name2 AS (SELECT ...) -- 第二个CTE
SELECT ... FROM cte_name1 JOIN cte_name2...; -- 主查询
2.2 多CTE链式组合
CTE的强大之处在于可以像管道一样串联:
sql复制WITH
raw_data AS (
SELECT * FROM sensor_readings WHERE created_at > NOW() - INTERVAL '
