CTE 与递归查询¶
核心问题:CTE 解决了什么问题?如何用递归 CTE 查询树形结构?
它解决了什么问题?¶
CTE(公共表表达式) 将复杂查询拆分为可读的命名子查询,解决两个问题: 1. 可读性:将多层嵌套子查询改写为线性结构,易于理解和维护 2. 递归查询:支持查询树形结构(组织架构、分类层级、评论回复等),这是普通 SQL 无法做到的
普通 CTE:提高可读性¶
-- ❌ 嵌套子查询:难以阅读
SELECT department, cnt FROM (
SELECT department, COUNT(*) AS cnt FROM (
SELECT * FROM employees WHERE salary > 10000
) high_salary GROUP BY department
) dept_stats WHERE cnt > 5;
-- ✅ CTE:线性结构,清晰易读
WITH
high_salary AS (
SELECT * FROM employees WHERE salary > 10000
),
dept_stats AS (
SELECT department, COUNT(*) AS cnt
FROM high_salary
GROUP BY department
)
SELECT * FROM dept_stats WHERE cnt > 5;
优点: - 命名子查询,语义清晰 - 可以被多次引用(避免重复写相同子查询) - 便于调试(可以单独查询某个 CTE)
递归 CTE:查询树形结构¶
语法结构:
WITH RECURSIVE cte_name AS (
-- 基础查询(锚点):递归的起点
SELECT ...
UNION ALL
-- 递归查询:引用 cte_name 自身
SELECT ... FROM table JOIN cte_name ON ...
)
SELECT * FROM cte_name;
实战:查询组织架构树¶
-- 表结构
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
manager_id INT -- NULL 表示顶级管理者
);
-- 递归 CTE:查询 id=1 的员工及其所有下属
WITH RECURSIVE org_tree AS (
-- 基础查询:从指定员工开始(锚点)
SELECT id, name, manager_id, 1 AS level
FROM employees
WHERE id = 1
UNION ALL
-- 递归查询:找所有直接下属
SELECT e.id, e.name, e.manager_id, ot.level + 1
FROM employees e
JOIN org_tree ot ON e.manager_id = ot.id
)
SELECT
REPEAT(' ', level - 1) || name AS org_chart, -- 缩进显示层级
level
FROM org_tree
ORDER BY level, id;
执行过程:
flowchart TD
A["第1次:锚点查询\n找到 id=1 的员工"] --> B["第2次:递归\n找 manager_id=1 的员工"]
B --> C["第3次:递归\n找上一步结果的下属"]
C --> D["...直到没有更多下属"]
D --> E["合并所有结果返回"]
实战:查询分类层级¶
-- 查询某个分类及其所有子分类(电商商品分类树)
WITH RECURSIVE category_tree AS (
SELECT id, name, parent_id, 0 AS depth
FROM categories
WHERE id = 100 -- 从"电子产品"分类开始
UNION ALL
SELECT c.id, c.name, c.parent_id, ct.depth + 1
FROM categories c
JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT * FROM category_tree ORDER BY depth, id;
工作中的坑¶
坑1:递归 CTE 死循环¶
-- ❌ 如果数据中存在循环引用(A 的上级是 B,B 的上级是 A),会无限递归
-- ✅ 添加深度限制防止无限递归
WITH RECURSIVE org_tree AS (
SELECT id, name, manager_id, 1 AS level
FROM employees WHERE id = 1
UNION ALL
SELECT e.id, e.name, e.manager_id, ot.level + 1
FROM employees e
JOIN org_tree ot ON e.manager_id = ot.id
WHERE ot.level < 10 -- ✅ 限制最大深度,防止死循环
)
SELECT * FROM org_tree;
坑2:CTE 在 PostgreSQL 中默认是优化屏障¶
-- PostgreSQL 12 之前,CTE 是优化屏障(Optimization Fence)
-- 即使外层有 WHERE 条件,CTE 也会全量执行,不会下推过滤条件
-- PostgreSQL 12+ 默认允许优化器内联 CTE
-- 如果需要强制 CTE 作为优化屏障(如需要 CTE 只执行一次)
WITH MATERIALIZED cte AS (...) -- 强制物化
WITH NOT MATERIALIZED cte AS (...) -- 允许内联优化
面试高频问题¶
Q:CTE 和子查询有什么区别?什么时候用 CTE?
CTE 是命名的子查询,可以被多次引用,代码更清晰;子查询是匿名的,嵌套多层后难以阅读。当查询逻辑复杂、需要多次引用同一子查询、或需要递归查询时,用 CTE。
Q:如何用 SQL 查询树形结构(如组织架构)?
使用递归 CTE(
WITH RECURSIVE):先写锚点查询(起始节点),再写递归查询(找子节点),用UNION ALL连接,直到没有更多子节点为止。注意添加深度限制防止循环引用导致死循环。