Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > mysql with recursive

MySQL With Recursive及语法结构详解(最新推荐)

作者:西凉的悲伤

这段文章详细介绍了MySQL 8.0版本引入的WITH RECURSIVE递归公共表表达式(RecursiveCTE)特性,解释了其用途、语法结构以及应用场景,它通过递归查询解决树形结构数据的遍历问题,并提供了多个实战案例

一、什么是 WITH RECURSIVE

WITH RECURSIVE 是 MySQL 提供的一种递归公共表表达式(Recursive Common Table Expression,Recursive CTE)

简单理解:

它允许 SQL 自己调用自己,实现递归查询。

类似于 Java 中:

public void test(Node node){
    test(node.getChild());
}

SQL 以前无法直接递归,只能:

WITH RECURSIVE 出现后,可以直接在 SQL 中完成树形结构遍历。

二、MySQL 从哪个版本开始支持?

MySQL 从:

MySQL 8.0

开始正式支持:

即:

WITH ...

WITH RECURSIVE ...

都是 MySQL 8.0 新增的特性。

MySQL 5.7 及以前:

❌ 不支持

三、它解决了什么问题

开发中经常出现树形结构:

组织架构

董事长
 ├── 总经理
 │    ├── 技术部
 │    └── 财务部
 └── 人事部

菜单系统

系统管理
 ├── 用户管理
 ├── 角色管理
 └── 权限管理

行政区划

中国
 ├── 北京
 ├── 上海
 └── 广东
      ├── 深圳
      └── 广州

评论回复

评论1
 ├── 回复1
 │    └── 回复2
 └── 回复3

以前查询:

查询所有子节点

需要:

select * from dept where parent_id=1;
select * from dept where parent_id in(...);
select * from dept where parent_id in(...);

不断循环。

现在:

WITH RECURSIVE

一条 SQL 搞定。

四、WITH RECURSIVE 语法结构

标准语法:

WITH RECURSIVE cte_name AS (
    -- 初始查询(锚点查询)
    SELECT ...
    UNION ALL
    -- 递归查询
    SELECT ...
    FROM table t
    JOIN cte_name c
      ON ...
)
SELECT * FROM cte_name;

五、递归执行过程

例如:

WITH RECURSIVE nums AS (
    SELECT 1 AS n
    UNION ALL
    SELECT n + 1
    FROM nums
    WHERE n < 5
)
SELECT * FROM nums;

执行步骤:

第一步

执行锚点查询

SELECT 1

结果:

1

第二步

带入递归部分

1 + 1

得到:

2

第三步

继续递归

3
4
5

结果:

1
2
3
4
5

六、WITH RECURSIVE 的组成部分

必须包含两部分:

1. Anchor(锚点)

递归起点

SELECT 1

2. Recursive(递归部分)

不断调用自身

SELECT n+1
FROM nums
WHERE n<5

3. UNION ALL

连接两部分

Anchor
UNION ALL
Recursive

七、第一个实战:生成数字序列

生成1~10

WITH RECURSIVE nums AS (
    SELECT 1 AS num
    UNION ALL
    SELECT num + 1
    FROM nums
    WHERE num < 10
)
SELECT * FROM nums;

结果:

1
2
3
4
5
6
7
8
9
10

八、生成日期序列

生成最近7天

WITH RECURSIVE dates AS (
    SELECT CURDATE() AS dt
    UNION ALL
    SELECT DATE_SUB(dt,INTERVAL 1 DAY)
    FROM dates
    WHERE dt > CURDATE()-INTERVAL 6 DAY
)
SELECT * FROM dates;

结果:

2026-06-15
2026-06-14
2026-06-13
...

九、树形结构实战

部门表

CREATE TABLE dept(
    id INT PRIMARY KEY,
    dept_name VARCHAR(50),
    parent_id INT
);

数据:

1 总公司 NULL
2 技术中心 1
3 财务中心 1
4 开发部 2
5 测试部 2
6 Java组 4
7 前端组 4

数据结构

总公司(1)
├── 技术中心(2)
│    ├── 开发部(4)
│    │    ├── Java组(6)
│    │    └── 前端组(7)
│    └── 测试部(5)
└── 财务中心(3)

十、递归向下查询(查询所有子节点)

查询部门1下面所有节点

WITH RECURSIVE dept_tree AS (
    SELECT
        id,
        dept_name,
        parent_id,
        1 level
    FROM dept
    WHERE id = 1
    UNION ALL
    SELECT
        d.id,
        d.dept_name,
        d.parent_id,
        dt.level + 1
    FROM dept d
    JOIN dept_tree dt
      ON d.parent_id = dt.id
)
SELECT *
FROM dept_tree;

结果:

1 总公司
2 技术中心
3 财务中心
4 开发部
5 测试部
6 Java组
7 前端组

十一、增加层级显示

WITH RECURSIVE dept_tree AS (
    SELECT
        id,
        dept_name,
        parent_id,
        1 level
    FROM dept
    WHERE id=1
    UNION ALL
    SELECT
        d.id,
        d.dept_name,
        d.parent_id,
        dt.level+1
    FROM dept d
    JOIN dept_tree dt
      ON d.parent_id=dt.id
)
SELECT
    id,
    dept_name,
    level
FROM dept_tree;

结果:

id  dept_name   level
1   总公司      1
2   技术中心    2
3   财务中心    2
4   开发部      3
5   测试部      3
6   Java组      4
7   前端组      4

十二、生成完整路径

很多权限系统都这样做。

例如:

总公司/技术中心/开发部/Java组

SQL:

WITH RECURSIVE dept_tree AS (
    SELECT
        id,
        dept_name,
        parent_id,
        dept_name AS path
    FROM dept
    WHERE id=1
    UNION ALL
    SELECT
        d.id,
        d.dept_name,
        d.parent_id,
        CONCAT(dt.path,'/',d.dept_name)
    FROM dept d
    JOIN dept_tree dt
      ON d.parent_id=dt.id
)
SELECT *
FROM dept_tree;

结果:

总公司
总公司/技术中心
总公司/技术中心/开发部
总公司/技术中心/开发部/Java组

十三、WITH RECURSIVE 能向上查吗?

答案:

完全可以。

很多人误以为只能向下查。

实际上:

递归方向由 JOIN 条件决定。

十四、向上递归查询祖先节点

例如:

查询 Java组(id=6) 的所有上级。

树:

总公司(1)
└── 技术中心(2)
     └── 开发部(4)
          └── Java组(6)

SQL:

WITH RECURSIVE parent_tree AS (
    SELECT
        id,
        dept_name,
        parent_id
    FROM dept
    WHERE id = 6
    UNION ALL
    SELECT
        d.id,
        d.dept_name,
        d.parent_id
    FROM dept d
    JOIN parent_tree pt
      ON d.id = pt.parent_id
)
SELECT *
FROM parent_tree;

结果:

6 Java组
4 开发部
2 技术中心
1 总公司

十五、向上生成完整路径

WITH RECURSIVE parent_tree AS (
    SELECT
        id,
        dept_name,
        parent_id,
        dept_name AS path
    FROM dept
    WHERE id=6
    UNION ALL
    SELECT
        d.id,
        d.dept_name,
        d.parent_id,
        CONCAT(d.dept_name,'/',pt.path)
    FROM dept d
    JOIN parent_tree pt
      ON d.id=pt.parent_id
)
SELECT *
FROM parent_tree
ORDER BY id;

最终得到:

总公司/技术中心/开发部/Java组

十六、避免死循环

假设数据错误:

1 -> 2
2 -> 3
3 -> 1

形成环:

1
↓
2
↓
3
↑
└───

递归将无限执行。

解决方法:

记录访问路径。

WITH RECURSIVE dept_tree AS (
    SELECT
        id,
        parent_id,
        CAST(id AS CHAR(1000)) path
    FROM dept
    WHERE id=1
    UNION ALL
    SELECT
        d.id,
        d.parent_id,
        CONCAT(dt.path,',',d.id)
    FROM dept d
    JOIN dept_tree dt
      ON d.parent_id=dt.id
    WHERE FIND_IN_SET(d.id,dt.path)=0
)
SELECT *
FROM dept_tree;

十七、递归层数限制

查看:

SHOW VARIABLES LIKE '%recursion%';

通常:

cte_max_recursion_depth = 1000

表示最多递归1000层。

修改:

SET SESSION cte_max_recursion_depth = 5000;

或者:

SET GLOBAL cte_max_recursion_depth = 5000;

十八、WITH RECURSIVE 使用规则总结

必须:

WITH RECURSIVE name AS(
    anchor
    UNION ALL
    recursive
)

递归部分必须引用自己

FROM name

必须有终止条件

WHERE level < 100

否则死循环。

推荐使用:

UNION ALL

而不是:

UNION

因为:

UNION

需要去重。

性能更差。

十九、企业开发中的典型应用

组织架构树

总公司
 └── 分公司
      └── 部门

查询所有下级。

RBAC权限菜单

系统管理
 ├── 用户管理
 ├── 角色管理
 └── 权限管理

加载菜单树。

评论回复

评论
 └── 回复
      └── 回复

查询完整评论链。

行政区域

中国
 └── 广东
      └── 深圳

查询省市区。

商品分类

电子产品
 └── 手机
      └── 安卓手机

查询所有分类。

二十、面试高频问题

Q1:WITH RECURSIVE 从哪个版本开始支持?

MySQL 8.0。

Q2:WITH 和 WITH RECURSIVE 区别?

WITH

普通CTE,不递归。

WITH t AS(
  SELECT *
  FROM user
)
SELECT * FROM t;
WITH RECURSIVE

支持递归调用自身。

Q3:能否查询父节点?

可以。

改变 JOIN 方向即可。

向下:

d.parent_id = tree.id

向上:

d.id = tree.parent_id

Q4:为什么推荐 WITH RECURSIVE?

相比循环查询:

总结

WITH RECURSIVE 是 MySQL 8.0 引入的递归查询能力,通过“锚点查询 + UNION ALL + 递归查询”的方式,可以优雅地处理组织架构、菜单树、评论树、行政区划、商品分类等层级数据,既能向下查询所有子孙节点,也能向上查询所有祖先节点,是现代 MySQL 树形数据查询的首选方案。

参考:

mysql递归查询语法WITH RECURSIVE

MySQL RECURSIVE Clauses

MySQL | Recursive CTE (Common Table Expressions)

到此这篇关于MySQL With Recursive及语法结构详解(最新推荐)的文章就介绍到这了,更多相关mysql with recursive内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文