【实战详解】MySQL 8.0递归查询终极教程:附组织架构/分类树完整代码
一、什么是递归查询?
递归查询就像俄罗斯套娃:通过一个查询反复调用自身,逐层解开嵌套的数据结构。
典型应用场景:
- 组织架构(上下级关系)
- 分类树(多级分类)
- 菜单权限树
- 评论回复嵌套
二、MySQL 8.0+ 的递归查询语法
基础语法结构
WITH RECURSIVE cte_name AS (-- 初始查询(锚点)SELECT ... FROM ...UNION ALL-- 递归部分SELECT ... FROM cte_name, other_tables...WHERE ...
)
SELECT * FROM cte_name;
三、实战示例
示例1:数字序列生成
-- 生成1到10的数字序列
WITH RECURSIVE number_sequence AS (SELECT 1 as n -- 初始值UNION ALLSELECT n + 1 -- 递归:每次+1FROM number_sequenceWHERE n < 10 -- 终止条件
)
SELECT * FROM number_sequence;
输出:
n
--
1
2
...
10
示例2:组织架构查询 #数据准备
CREATE TABLE employees (id INT PRIMARY KEY,name VARCHAR(50),manager_id INT
);INSERT INTO employees VALUES
(1, 'CEO', NULL),
(2, 'CTO', 1),
(3, '技术总监', 2),
(4, '开发经理', 3),
(5, '开发工程师A', 4),
(6, '开发工程师B', 4),
(7, '产品总监', 2);
#递归查询:找出某员工的所有下属
WITH RECURSIVE employee_tree AS (-- 初始:从CTO开始SELECT id, name, manager_id, 0 as levelFROM employeesWHERE name = 'CTO'UNION ALL-- 递归:逐级向下查找SELECT e.id, e.name, e.manager_id, et.level + 1FROM employees eINNER JOIN employee_tree et ON e.manager_id = et.id
)
SELECT LPAD('', level * 4, ' ') || name as 组织架构,level as 层级
FROM employee_tree;
输出:
组织架构 层级-
CTO 0技术总监 1产品总监 1开发经理 2开发工程师A 3开发工程师B 3
四、分类树查询
数据准备
CREATE TABLE categories (id INT PRIMARY KEY,name VARCHAR(50),parent_id INT
);INSERT INTO categories VALUES
(1, '电子产品', NULL),
(2, '手机', 1),
(3, '电脑', 1),
(4, '智能手机', 2),
(5, '功能手机', 2),
(6, '笔记本电脑', 3),
(7, '台式机', 3),
(8, '游戏本', 6);
查询完整分类路径
WITH RECURSIVE category_path AS (SELECT id,name,parent_id,name as pathFROM categoriesWHERE parent_id IS NULL -- 根节点UNION ALLSELECT c.id,c.name,c.parent_id,CONCAT(cp.path, ' > ', c.name)FROM categories cINNER JOIN category_path cp ON c.parent_id = cp.id
)
SELECT * FROM category_path;
输出:
id name 父级ID path-- - -
1 电子产品 NULL 电子产品
2 手机 1 电子产品 > 手机
3 电脑 1 电子产品 > 电脑
4 智能手机 2 电子产品 > 手机 > 智能手机
5 功能手机 2 电子产品 > 手机 > 功能手机
6 笔记本电脑 3 电子产品 > 电脑 > 笔记本电脑
7 台式机 3 电子产品 > 电脑 > 台式机
8 游戏本 6 电子产品 > 电脑 > 笔记本电脑 > 游戏本
五、评论回复嵌套查询
数据准备
CREATE TABLE comments (id INT PRIMARY KEY,content TEXT,parent_comment_id INT,user_id INT
);INSERT INTO comments VALUES
(1, '这篇文章很棒!', NULL, 101),
(2, '感谢分享', 1, 102),
(3, '我不同意这个观点', 1, 103),
(4, '能详细解释一下吗?', 3, 104),
(5, '请看参考文献', 4, 103);
查询评论树
WITH RECURSIVE comment_tree AS (SELECT id,content,parent_comment_id,user_id,0 as depth,CAST(id AS CHAR(100)) as pathFROM commentsWHERE parent_comment_id IS NULLUNION ALLSELECT c.id,c.content,c.parent_comment_id,c.user_id,ct.depth + 1,CONCAT(ct.path, '-', c.id)FROM comments cINNER JOIN comment_tree ct ON c.parent_comment_id = ct.id
)
SELECT LPAD('', depth * 4, ' ') || content as 评论内容,depth as 嵌套深度
FROM comment_tree
ORDER BY path;
六、高级技巧
- 限制递归深度
WITH RECURSIVE limited_tree AS (SELECT id, name, manager_id, 0 as levelFROM employeesWHERE id = 1UNION ALLSELECT e.id, e.name, e.manager_id, lt.level + 1FROM employees eINNER JOIN limited_tree lt ON e.manager_id = lt.idWHERE lt.level < 3 -- 限制最大深度为3层
)
SELECT * FROM limited_tree;
- 防止循环引用
WITH RECURSIVE no_cycle AS (SELECT id, name, manager_id, 0 as level,CAST(id AS CHAR(100)) as pathFROM employeesWHERE id = 1UNION ALLSELECT e.id, e.name, e.manager_id, nc.level + 1,CONCAT(nc.path, '-', e.id)FROM employees eINNER JOIN no_cycle nc ON e.manager_id = nc.idWHERE FIND_IN_SET(e.id, nc.path) = 0 -- 防止循环
)
SELECT * FROM no_cycle;
- 统计每层数量
WITH RECURSIVE level_count AS (SELECT id, name, manager_id, 0 as levelFROM employeesWHERE manager_id IS NULLUNION ALLSELECT e.id, e.name, e.manager_id, lc.level + 1FROM employees eINNER JOIN level_count lc ON e.manager_id = lc.id
)
SELECT level as 层级,COUNT(*) as 人数
FROM level_count
GROUP BY level
ORDER BY level;
七、性能优化建议
- 创建索引
-- 为递归查询的关联字段创建索引
CREATE INDEX idx_manager_id ON employees(manager_id);
CREATE INDEX idx_parent_id ON categories(parent_id);
- 控制递归深度
-- 设置最大递归深度
SET SESSION cte_max_recursion_depth = 1000;
- 使用 EXISTS 优化
WITH RECURSIVE optimized_tree AS (SELECT id, name, manager_id, 0 as levelFROM employees e1WHERE NOT EXISTS (SELECT 1 FROM employees e2 WHERE e2.manager_id = e1.id) -- 从叶子节点开始UNION ALLSELECT e.id, e.name, e.manager_id, ot.level + 1FROM employees eINNER JOIN optimized_tree ot ON e.id = ot.manager_id
)
SELECT * FROM optimized_tree;
八、常见错误与解决
❌ 错误:无限递归
-- 错误示例:缺少终止条件
WITH RECURSIVE infinite AS (SELECT 1 as nUNION ALLSELECT n + 1 FROM infinite -- 没有WHERE条件!
)
SELECT * FROM infinite;
解决: 确保递归部分有明确的终止条件
❌ 错误:循环引用
-- 数据中存在 A→B→A 的循环时
WITH RECURSIVE cycle AS (SELECT id, name, manager_idFROM employeesWHERE id = 1UNION ALLSELECT e.id, e.name, e.manager_idFROM employees eINNER JOIN cycle c ON e.manager_id = c.id
)
SELECT * FROM cycle; -- 可能无限循环
解决: 使用路径追踪防止循环
九、版本兼容性
| MySQL 版本 | 递归查询支持 | ||--| | 5.7 及以下 | ❌ 不支持 | | 8.0+ | ✅ 支持 |
替代方案(5.7及以下):
- 应用程序层递归处理
- 存储过程实现递归逻辑
- 维护路径字段(如
path = '1/2/3'
)
十、实用模板
通用递归查询模板
WITH RECURSIVE custom_tree AS (-- 初始查询(锚点成员)SELECT [columns],0 as level,CAST([id_column] AS CHAR(255)) as pathFROM [table_name]WHERE [root_condition] -- 指定起点UNION ALL-- 递归查询(递归成员)SELECT t.[columns],ct.level + 1,CONCAT(ct.path, '-', t.[id_column])FROM [table_name] tINNER JOIN custom_tree ct ON t.[parent_column] = ct.[id_column]WHERE [termination_condition] -- 终止条件
)
SELECT * FROM custom_tree;
总结
递归查询 = 初始查询 + 递归部分 + 终止条件
适用场景:
- ✅ 层次数据结构
- ✅ 树状关系查询
- ✅ 路径枚举
- ❌ 简单线性查询(用普通查询即可)
记住关键点:
- 必须使用
WITH RECURSIVE
- 包含
UNION ALL
连接初始和递归部分 - 明确的终止条件防止无限递归
- MySQL 8.0+ 才支持此功能
通过递归查询,你可以轻松处理复杂的层次数据关系,让数据库帮你完成"一层层剥开"的逻辑! 另外搭配便捷的80kmMYSQL备份工具,可定时备份、异地备份,MYSQL导出导入。可本地连接LINUX里的MYSQL,简单便捷。可以大大地提高工作效率喔。