Skip to content

CTE 与递归查询 ​

核心问题:CTE 解决了什么问题?如何用递归 CTE 查询树形结构?

1. 它解决了什么问题? ​

CTE(公共表表达式) 将复杂查询拆分为可读的命名子查询,解决两个问题:

  1. 可读性:将多层嵌套子查询改写为线性结构,易于理解和维护
  2. 递归查询:支持查询树形结构(组织架构、分类层级、评论回复等),这是普通 SQL 无法做到的

2. 普通 CTE:提高可读性 ​

sql
-- ❌ 嵌套子查询:难以阅读
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;

3. 递归 CTE:查询树形结构 ​

语法结构:

sql
WITH RECURSIVE cte_name AS (
    -- 基础查询(锚点):递归的起点
    SELECT ...
    
    UNION ALL
    
    -- 递归查询:引用 cte_name 自身
    SELECT ... FROM table JOIN cte_name ON ...
)
SELECT * FROM cte_name;

4. 实战:查询组织架构树 ​

sql
-- 表结构
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;

执行过程:

5. 实战:查询分类层级 ​

sql
-- 查询某个分类及其所有子分类(电商商品分类树)
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;

6. 工作中的坑 ​

6.1 递归 CTE 死循环 ​

sql
-- ❌ 如果数据中存在循环引用(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;

6.2 CTE 优化屏障(PG 12+) ​

sql
-- PostgreSQL 12 之前,CTE 是优化屏障(Optimization Fence)
-- PostgreSQL 12+ 默认允许优化器内联 CTE

-- 如果需要强制 CTE 作为优化屏障(如需要 CTE 只执行一次)
WITH MATERIALIZED cte AS (...)  -- 强制物化
WITH NOT MATERIALIZED cte AS (...) -- 允许内联优化

Released under the MIT License.