MySQL 递归 CTE:WITH RECURSIVE 用法详解

文章目录

在实际开发中,经常会遇到树形数据,例如:

  • 组织架构

  • 区域层级

  • 菜单树

  • 分类树

  • 网格管理

这类表通常都有两个核心字段:

text 复制代码
id
parent_id

例如:

text 复制代码
北京大区 A
├── 朝阳区域 B
│   ├── 网格1 D
│   └── 网格2 E
└── 海淀区域 C
    └── 网格3 F

MySQL 8.0+ 可以使用 WITH RECURSIVE 递归查询整棵树。

一、创建测试表

sql 复制代码
CREATE TABLE area (
    id VARCHAR(20) PRIMARY KEY,
    parent_id VARCHAR(20),
    name VARCHAR(50)
);

插入测试数据:

sql 复制代码
INSERT INTO area VALUES
('A', NULL, '北京大区'),
('B', 'A', '朝阳区域'),
('C', 'A', '海淀区域'),
('D', 'B', '网格1'),
('E', 'B', '网格2'),
('F', 'C', '网格3');

二、查询当前节点及所有子节点

sql 复制代码
WITH RECURSIVE area_tree AS (
    SELECT id, parent_id, name
    FROM area
    WHERE id = 'A'

    UNION ALL

    SELECT a.id, a.parent_id, a.name
    FROM area a
    INNER JOIN area_tree t
        ON a.parent_id = t.id
)
SELECT *
FROM area_tree;

结果:

text 复制代码
A 北京大区
B 朝阳区域
C 海淀区域
D 网格1
E 网格2
F 网格3

这里的:

sql 复制代码
area_tree

不是数据库真实表,而是当前 SQL 执行期间产生的临时结果集。

三、递归原理

第一次先查询起点:

sql 复制代码
SELECT *
FROM area
WHERE id = 'A';

得到:

text 复制代码
A

然后通过:

sql 复制代码
a.parent_id = t.id

寻找 A 的子节点:

text 复制代码
B、C

再继续寻找 B、C 的子节点:

text 复制代码
D、E、F

直到没有子节点,递归结束。

可以简单理解为:

text 复制代码
起点
 ↓
找子节点
 ↓
再找子节点的子节点
 ↓
直到结束

四、只查询下级,不包含自己

如果只想查询 A 的所有下级,不包含 A:

sql 复制代码
WITH RECURSIVE area_tree AS (
    SELECT id, parent_id, name, 0 AS level
    FROM area
    WHERE id = 'A'

    UNION ALL

    SELECT a.id, a.parent_id, a.name, t.level + 1
    FROM area a
    INNER JOIN area_tree t
        ON a.parent_id = t.id
)
SELECT *
FROM area_tree
WHERE level > 0;

结果:

text 复制代码
B
C
D
E
F

五、向上查询父节点

如果从网格1开始查询所有上级:

sql 复制代码
WITH RECURSIVE area_parent AS (
    SELECT id, parent_id, name
    FROM area
    WHERE id = 'D'

    UNION ALL

    SELECT a.id, a.parent_id, a.name
    FROM area a
    INNER JOIN area_parent t
        ON a.id = t.parent_id
)
SELECT *
FROM area_parent;

结果:

text 复制代码
网格1
朝阳区域
北京大区

可以简单记住:

向下找子节点:

sql 复制代码
child.parent_id = parent.id

向上找父节点:

sql 复制代码
parent.id = child.parent_id

六、MyBatis 多节点查询

如果前端传多个节点,可以配合 <foreach>

xml 复制代码
WITH RECURSIVE area_tree AS (
    SELECT id, parent_id
    FROM area
    WHERE id IN
    <foreach collection="nodeIds"
             item="id"
             open="("
             separator=","
             close=")">
        #{id}
    </foreach>

    UNION ALL

    SELECT a.id, a.parent_id
    FROM area a
    INNER JOIN area_tree t
        ON a.parent_id = t.id
)
SELECT *
FROM area_tree

这种方式很适合:

  • 区域权限

  • 组织权限

  • 菜单权限

  • 网格层级查询

七、性能注意

树形查询建议给 parent_id 建索引:

sql 复制代码
CREATE INDEX idx_parent_id
ON area(parent_id);

因为递归过程中会不断根据 parent_id 查询子节点。

另外要避免出现循环关系:

text 复制代码
A → B → C → A

否则会导致递归异常。

八、总结

WITH RECURSIVE 的基本结构:

sql 复制代码
WITH RECURSIVE tree AS (
    -- 起点
    SELECT ...

    UNION ALL

    -- 递归查询
    SELECT ...
    FROM table
    JOIN tree ON ...
)
SELECT *
FROM tree;

一句话理解:

WITH RECURSIVE 就是从一个节点开始,一层一层查询父节点或子节点。

常用于:

  • 组织架构

  • 菜单树

  • 区域树

  • 权限树

  • 分类树

  • 网格层级

注意:MySQL 需要 8.0+ 才支持 WITH RECURSIVE

相关推荐
数据狐(Datafox)1 小时前
mercadolibre.item_get 工程实战:美客多商品详情API技术解析与落地应用
java·人工智能·mysql·json
恋猫de小郭1 小时前
聊个比较有意思的 Flutter Web 问题
android·前端·flutter
stevenzqzq1 小时前
Compose 生命周期与副作用的核心奥秘
android·compose
2501_915918411 小时前
Flutter项目配置iOS混淆的详细步骤与工具推荐
android·flutter·ios·小程序·uni-app·cocoa·iphone
天空之城--1 小时前
过去一周Android行业动态:编码技术与综合趋势精选
android·flutter·性能优化·kotlin·android jetpack
脚踏实地,坚持不懈!2 小时前
Android 上层卡顿在内核中的反应(基于 Linux v7.2.2)
android·linux·运维
行云&流水2 小时前
自动备份mysql数据库
数据库·mysql
IMPYLH2 小时前
HTML 的 <option> 元素
服务器·数据库·html