MySQL 8.0 递归 CTE:树形结构一键生成层级 Path 并更新回表

一、业务场景

在做问题分类、部门、菜单、商品类目等树形结构时,经常需要:

  • 一次查出所有层级数据
  • 自动生成 path(如:一级/二级/三级
  • path 永久更新回表

本文以问题分类表 sys_problem_category 为例,手把手实现。

二、表结构

sql

sql 复制代码
CREATE TABLE sys_problem_category (
    problem_code VARCHAR(50) PRIMARY KEY,
    problem_name VARCHAR(100),
    parent_code  VARCHAR(50),
    path          VARCHAR(500) COMMENT '层级路径'
);

三、需求

根据 problem_code / parent_code 关系,自动生成:

plaintext

复制代码
根:SH001 售后问题
子:SH001 售后问题/DXBH001 对象编号问题

并把 path 更新回表


四、完整 SQL(查询 + 更新)

1. 先查询预览(安全核对)

sql

sql 复制代码
WITH RECURSIVE tree AS (
    -- 1. 初始部分(Anchor):根节点
    SELECT
        problem_code,
        problem_name,
        parent_code,
        CAST(CONCAT(problem_code, ' ', problem_name) AS VARCHAR(500)) AS path
    FROM sys_problem_category
    WHERE parent_code IS NULL OR parent_code = ''

    UNION ALL

    -- 2. 递归部分(Recursive):关联自己tree,找子节点
    SELECT
        t.problem_code,
        t.problem_name,
        t.parent_code,
        CAST(CONCAT(tr.path, '/', t.problem_code, ' ', t.problem_name) AS VARCHAR(500)) AS path
    FROM sys_problem_category t
    JOIN tree tr 
      ON t.parent_code = tr.problem_code
)
SELECT * FROM tree ORDER BY path;

2. 直接更新回表

sql

sql 复制代码
WITH RECURSIVE tree AS (
    SELECT
        problem_code,
        problem_name,
        parent_code,
        CAST(CONCAT(problem_code, ' ', problem_name) AS VARCHAR(500)) AS path
    FROM sys_problem_category
    WHERE parent_code IS NULL OR parent_code = ''

    UNION ALL

    SELECT
        t.problem_code,
        t.problem_name,
        t.parent_code,
        CAST(CONCAT(tr.path, '/', t.problem_code, ' ', t.problem_name) AS VARCHAR(500)) AS path
    FROM sys_problem_category t
    JOIN tree tr 
      ON t.parent_code = tr.problem_code
)
UPDATE sys_problem_category c
JOIN tree t 
  ON c.problem_code = t.problem_code
SET c.path = t.path;

五、重点:WITH RECURSIVE 为什么能 "关联自己"?

很多人第一次看到下面这段时会懵:

sql

sql 复制代码
WITH RECURSIVE tree AS (
    -- 初始
    SELECT ... 
    UNION ALL
    -- 递归:FROM sys_problem_category t JOIN tree tr
    SELECT ...
)

tree 是刚定义的,为什么下面就能 JOIN 它?

1. 递归 CTE 固定结构

WITH RECURSIVE两部分 组成,用 UNION ALL 连接:

plaintext

sql 复制代码
WITH RECURSIVE cte AS (
    初始查询(Anchor)  -- 只执行1次,不引用cte
    UNION ALL
    递归查询(Recursive)-- 反复执行,引用cte自己
)

MySQL 把这两部分当成一个整体递归单元 ,允许第二部分引用自己的名字 cte(这里是 tree)MySQL。

2. 执行过程(迭代式,不是 "同时存在")

不是 "外层 tree 已经全部算好,再 JOIN",而是迭代生成

  1. 第 1 轮:执行初始部分

    • 查出所有根节点 ,放入临时结果集 tree(此时只有根)MySQL。
  2. 第 2 轮:执行递归部分

    sql

    css 复制代码
    FROM sys_problem_category t JOIN tree tr
    ON t.parent_code = tr.problem_code
    • 这里的 tree = 上一轮结果(根节点)
    • 找到根的直接子节点 ,拼 path,追加到 treeMySQL。
  3. 第 3 轮:再次执行递归部分

    • 此时 tree = 根 + 一级子节点
    • 找到一级子节点的子节点(二级) ,继续追加MySQL。
  4. ...... 反复迭代

    • 直到递归部分查不到新数据(没有更多子节点),停止MySQL。

一句话总结:

JOIN tree tr 不是关联 "最终的大表",而是关联上一轮迭代的结果,层层往下钻,直到叶子节点MySQL。

3. 为什么不会死循环?

  • 树结构天然无环(parent_code 不会循环指向自己)
  • 每次迭代只找子节点,不会回头找父节点
  • 无新行时自动终止MySQL

六、执行结果示例

原始数据:

表格

problem_code problem_name parent_code path
SH001 售后问题 null
DXBH001 对象编号问题 SH001

执行后:

表格

problem_code problem_name parent_code path
SH001 售后问题 null SH001 售后问题
DXBH001 对象编号问题 SH001 SH001 售后问题 / DXBH001 对象编号问题

七、适用场景 & 注意事项

  1. 适用 :菜单、部门、分类、组织架构等单父节点树形结构
  2. MySQL 版本 :必须 8.0+ (5.7 不支持 WITH RECURSIVE
  3. 安全:先执行查询预览,确认 path 格式正确再 UPDATE
  4. 维护:新增 / 修改节点后,重新跑一次 UPDATE 即可刷新全表 path

八、总结

  • WITH RECURSIVE = 初始查询 + 递归查询
  • 递归部分 JOIN tree关联上一轮迭代结果,层层向下
  • 一次 SQL 搞定查询全树 + 生成 path + 更新回表,简洁高效

递归 CTE 是处理树形结构的 "神器",理解了迭代执行过程,就再也不会疑惑 "为什么能关联自己" 了。

相关推荐
徒手猫4 小时前
MySQL 窗口函数完全指南
数据库·mysql
betazhou5 小时前
电科金仓数据库V9 MySQL兼容版本搭建一主一从体验
数据库·mysql·oracle·主从·高可用·kingbase·v9 mysql兼容版本
砍材农夫5 小时前
物联网 基于netty核心实战-会话管理
后端
Mahir085 小时前
MyBatis 深度解密:从执行流程到底层原理全解
java·后端·面试·mybatis
孟林洁6 小时前
Java转AI应用开发速成(3)—— 第一个 SpringAI 聊天应用
java·spring boot·后端·ai·机器人
小村儿6 小时前
连载11- Claude code 的 Agent Teams——当子 Agent 开始互相说话
前端·后端·ai编程
wbs_scy6 小时前
MySQL 多表连接查询实战:内连接 + 外连接
数据库·mysql
折哥的程序人生 · 物流技术专研6 小时前
《Java 100 天进阶之路》第35篇:Java异常处理最佳实践
java·开发语言·后端·面试·求职招聘
用户8356290780516 小时前
使用 Python 创建 PowerPoint SmartArt 图形
后端·python