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

相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
小白男神2 天前
MySQL进阶学习四(存储过程、游标、触发器)
mysql
这个DBA有点耶2 天前
MVCC深入:Read View、版本链与快照读——InnoDB并发控制的内核
数据库·mysql·架构
DBA_G2 天前
从地面到云霄:GBase数据库在民航三大场景的落地实践
数据库
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
自由能燃气设备2 天前
商用全预混低氮冷凝锅炉免费方案vs付费方案对比+选型避坑指南
大数据·数据库·人工智能
科创致远2 天前
科创致远 ESOP 系统核心效能与实战价值展示
大数据·数据库·人工智能·精益工程
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android