MYSQL中WITH RECURSIVE递归查询的实现

 更新时间:2025年08月26日 09:45:53   作者:五月天的尾巴  
MySQL 8.0引入WITH RECURSIVE递归查询,用于处理层级数据,本文主要介绍了MYSQL中WITH RECURSIVE递归查询的实现,具有一定的参考价值,感兴趣的可以了解一下

一、前言

MySQL 8.0开始,可以使用WITH RECURSIVE来创建递归公用表表达式(Common Table Expressions , CTE)。

递归查询在各种应用场景中都很常见,例如:

  • 组织架构查询: 获取公司内部的组织结构信息,从CEO到普通员工的层级关系。
  • 产品分类查询: 从顶级分类到子分类的层级查找。
  • 目录结构查询: 在文件系统中,从根目录到子目录的路径查找。

二、语法

WITH RECURSIVE cte_name (column_list) AS (
	SELECT
		initial_query -- 初始查询
	UNION [ALL]
		recursive_query  -- 递归查询
)
SELECT * FROM cte_name;

其中:

  • CTE 名称(cte_name)用于标识递归查询的临时结果集。
  • 列名列表(column_list)定义了 CTE 结果集中包含的列及其名称。
  • 初始查询(initial_query)提供递归过程的起点,即第一次迭代时使用的数据。
  • 递归部分(递归子查询)定义了如何将前一次迭代的结果作为输入,计算出下一次迭代的数据。
  • recursive_query:表示递归查询语句,应当与column_list中的列名对应。
  • SELECT * FROM cte_name:表示最终返回的查询结果集,可以通过cte_name查询表中的列名进行指定。

1.递归查询的结构
递归查询通常由两部分构成:初始化查询(非递归部分)递归子查询(递归部分)

  • 初始化查询: 定义递归开始时的基础数据集,通常是与递归逻辑相关的最顶层数据或边界条件。
  • 递归子查询: 定义如何根据前一次迭代的结果生成下一次迭代的数据。递归子查询通常包含对自身 CTE 名称的引用,以递归地应用相同的操作。

2.连接操作符:

  • 递归查询的初始化查询递归查询通常通过 UNIONUNION ALL 连接起来,形成一个完整的递归查询表达式。
  • UNION 会去除结果集中的重复行,而 UNION ALL 不会去除重复,根据实际需求选择合适的连接操作符。

3.终止条件

  • 递归查询必须有一个明确的终止条件,否则会无限循环下去。终止条件通常隐含在递归子查询的 WHERE 子句或其他逻辑中,当满足特定条件时,不再产生新的结果。

三、示例

通过以下目录树示例查询父节点、子节点、及全链路等信息。

3.1、创建测试表和数据

CREATE TABLE tree_table (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    parent_id INT
);

INSERT INTO tree_table (id, name, parent_id) VALUES
(1, 'A', NULL),
(2, 'B', 1),
(3, 'C', 1),
(4, 'D', 2),
(5, 'E', 3);
(6, 'F', 3);
(7, 'G', 5);

3.2、查询所有子节点(以 id=1 为例)

WITH RECURSIVE cte AS (
    SELECT id, parent_id, name
    FROM tree_table
    WHERE id = 1
    UNION ALL
    SELECT t.id, t.parent_id, t.name
    FROM tree_table t
    INNER JOIN cte ON t.parent_id = cte.id
)
SELECT * FROM cte;

结果:

3.3、查询所有父节点(以 id=5 为例)

WITH RECURSIVE cte AS (
    SELECT id, parent_id, name
    FROM tree_table
    WHERE id = 5
    UNION ALL
    SELECT t.id, t.parent_id, t.name
    FROM tree_table t
    INNER JOIN cte ON t.id = cte.parent_id
)
SELECT * FROM cte;

注意:子节点与父节点的区别在于join的关联条件on的不同。

结果:

3.4、查询所有子节点(向下递归)并显示全链路路径及层级

查询节点下的所有子节点,并且可以看到全链路及层级,如A->B->C

WITH RECURSIVE cte AS (
    SELECT
        id,parent_id, name,
        name AS path,   -- 初始路径
        1 AS level      -- 起始层级
    FROM tree_table
    WHERE id = 1  -- 查询ID=1的所有子节点
    UNION ALL
    SELECT
        t.id,t.parent_id,t.name,
        CONCAT(cte.path, '->', t.name),  -- 路径拼接
        cte.level + 1                    -- 层级递增
    FROM tree_table t
    INNER JOIN cte ON t.parent_id = cte.id
)
SELECT * FROM cte;

结果如下:

3.5、查询所有父节点(向上递归)并显示全链路路径及层级

查询节点的所有父节点,并且可以看到全链路及层级,如A->B->C

WITH RECURSIVE cte AS (
    SELECT
        id, parent_id, name,
        CAST(name AS CHAR(255)) AS path,  -- 路径初始化
        1 AS level                        -- 起始层级
    FROM tree_table
    WHERE id = 6  -- 查询ID=6的所有父节点
    UNION ALL
    SELECT
        t.id, t.parent_id, t.name,
        CONCAT(t.name, '->', cte.path),  -- 向前拼接路径
        cte.level + 1                    -- 层级递增
    FROM tree_table t
    INNER JOIN cte ON t.id = cte.parent_id
)
SELECT * FROM cte
ORDER BY level ASC;  -- 按层级升序排列

结果如下:

3.6、高级功能:双向递归查询(同时获取祖先和后代)

-- 获取ID=5的所有祖先和后代
WITH RECURSIVE
down AS ( /* 向下递归获取后代 */
    SELECT id, parent_id, name, name AS path, 1 AS level
    FROM tree_table WHERE id = 5
    UNION ALL
    SELECT t.id, t.parent_id, t.name,
           CONCAT(down.path, '->', t.name),
           down.level + 1
    FROM tree_table t
    INNER JOIN down ON t.parent_id = down.id
),
up AS ( /* 向上递归获取祖先 */
    SELECT id, parent_id, name, name AS path, 1 AS level
    FROM tree_table WHERE id = 5
    UNION ALL
    SELECT t.id, t.parent_id, t.name,
           CONCAT(t.name, '->', up.path),
           up.level + 1
    FROM tree_table t
    INNER JOIN up ON t.id = up.parent_id
)
SELECT * FROM up WHERE id != 5  -- 排除重复的当前节点
UNION ALL
SELECT * FROM down;

结果如下:

上图示例中同时查询了父节点与子节点,可能从返回结果中不能区别出哪些是父节点哪些是子节点。这里改造一下:

-- 获取ID=5的所有祖先和后代
WITH RECURSIVE
down AS ( /* 向下递归获取后代 */
    SELECT id, parent_id, name, name AS path, 1 AS level,'down' as type
    FROM tree_table WHERE id = 5
    UNION ALL
    SELECT t.id, t.parent_id, t.name,
           CONCAT(down.path, '->', t.name),
           down.level + 1,
           'down' as type
    FROM tree_table t
    INNER JOIN down ON t.parent_id = down.id
),
up AS ( /* 向上递归获取祖先 */
    SELECT id, parent_id, name, name AS path, 1 AS level,'up' as type
    FROM tree_table WHERE id = 5
    UNION ALL
    SELECT t.id, t.parent_id, t.name,
           CONCAT(t.name, '->', up.path),
           up.level + 1,
           'up' as type
    FROM tree_table t
    INNER JOIN up ON t.id = up.parent_id
)
SELECT * FROM up  WHERE id != 5  -- 排除重复的当前节点
UNION ALL
SELECT * FROM down;

添加type字段区分父节点、子节点。

四、扩展

4.1、注意事项:

  1. 递归深度限制: MySQL 默认递归深度为 1000,超限会报错。可通过设置会话变量调整:
SET SESSION cte_max_recursion_depth = 10000; -- 修改递归深度
  1. 避免死循环: 确保数据中没有循环引用(如 A→B→A)。

  2. 性能: 大数据量时确保 parent_id 字段有索引:

CREATE INDEX idx_parent_id ON tree_table(parent_id);
  1. 路径长度限制:
CAST(name AS CHAR(255))  -- 根据实际需要调整长度

4.2、避免死循环

避免死循环: 确保数据中没有循环引用(如 A→B→A

如果遇到循环引用会导致递归查询无限循环查询,从而引发数据库异常报错。

解决方法:

  • 方法一:避免死循环,确保数据中没有循环引用(如 A→B→A
  • 方法二:限制递归层级,查询时设置递归层级,如小于10级

4.3、结果排序技巧

  • 按层级排序:ORDER BY level ASC
  • 按路径排序:ORDER BY full_path ASC
  • 按树形结构排序:
ORDER BY 
  LENGTH(full_path) - LENGTH(REPLACE(full_path, '->', '')),
  full_path

到此这篇关于MYSQL中WITH RECURSIVE递归查询的实现的文章就介绍到这了,更多相关MYSQL WITH RECURSIVE递归查询内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • mysql Community Server 5.7.19安装指南(详细)

    mysql Community Server 5.7.19安装指南(详细)

    这篇文章主要介绍了mysql Community Server 5.7.19安装指南(详细),需要的朋友可以参考下
    2017-10-10
  • MySQL加索引会导致数据库锁表吗

    MySQL加索引会导致数据库锁表吗

    本文详细介绍了MySQL在线DDL操作的机制,包括不同版本的影响、索引类型和操作方式对锁表的影响,以及如何查看和监控DDL操作的锁机制,通过实际案例和最佳实践,提供了在生产环境中高效执行DDL操作的建议,感兴趣的朋友跟随小编一起看看吧
    2026-01-01
  • MySQL中空值和NULL的区别小结

    MySQL中空值和NULL的区别小结

    在 MySQL 中,NULL 值和空值是两个不同的概念,本文主要介绍了MySQL中空值和NULL的区别小结,具有一定的参考价值,感兴趣的可以了解一下
    2024-08-08
  • Linux安装MySQL教程(二进制分发版)

    Linux安装MySQL教程(二进制分发版)

    这篇文章主要为大家详细介绍了Linux安装MySQL教程,二进制分发版,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-02-02
  • MySQL主从同步诊断show slave status 卡住的深度排查与解决方案(最新推荐)

    MySQL主从同步诊断show slave status 卡住的深度排查与解决方案(最新推荐)

    在 MySQL主从架构的日常运维中,show slave status是监控同步状态的核心命令,本文结合源码分析与调试实践,揭示其背后的锁竞争机制,并提供系统性的排查与优化方案,感兴趣的朋友一起看看吧
    2025-06-06
  • mysql实现游标分页的方法详解

    mysql实现游标分页的方法详解

    这篇文章主要为大家详细介绍了mysql实现游标分页的相关方法,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-10-10
  • mysql千万级数据分页查询性能优化

    mysql千万级数据分页查询性能优化

    本文给大家分享的是作者在使用mysql进行千万级数据量分页查询的时候进行性能优化的方法,非常不错的一篇文章,对我们学习mysql性能优化非常有帮助
    2017-11-11
  • Navicat导入导出Mysql 结构、数据、结构+数据完整步骤

    Navicat导入导出Mysql 结构、数据、结构+数据完整步骤

    导出表结构是指将数据库中的表的结构信息导出为SQL脚本或其他格式的文件,这个功能非常实用,特别是在数据库迁移或备份时,这篇文章主要给大家介绍了关于Navicat导入导出Mysql 结构、数据、结构+数据的相关资料,需要的朋友可以参考下
    2024-08-08
  • Mysql分组查询取最新的几种方案总结

    Mysql分组查询取最新的几种方案总结

    在写报表功能时遇到一个需要根据用户id分组查询最新一条钱包明细数据的需求,下面这篇文章主要给大家总结介绍了关于Mysql分组查询取最新的几种方案,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-05-05
  • 教你自动恢复MySQL数据库的日志文件(binlog)

    教你自动恢复MySQL数据库的日志文件(binlog)

    如果MySQL服务器启用了二进制日志,你可以使用mysqlbinlog工具来恢复从指定的时间点开始
    2014-05-05

最新评论