文章目录
- 一、创建测试表
- 二、查询当前节点及所有子节点
- 三、递归原理
- 四、只查询下级,不包含自己
- 五、向上查询父节点
- [六、MyBatis 多节点查询](#六、MyBatis 多节点查询)
- 七、性能注意
- 八、总结
在实际开发中,经常会遇到树形数据,例如:
-
组织架构
-
区域层级
-
菜单树
-
分类树
-
网格管理
这类表通常都有两个核心字段:
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。