0

0

SQL递归查询实战 WITH子句实现树形结构遍历

雪夜

雪夜

发布时间:2025-07-08 12:56:02

|

244人浏览过

|

来源于php中文网

原创

sql递归查询通过with recursive子句实现树形结构遍历,有效解决传统join难以处理的动态层级数据问题。1. with recursive由锚点成员和递归成员组成,通过union all连接,前者定义初始查询条件,如从特定节点(如alice)开始;2. 后者不断迭代查找下级节点,直到无新记录生成为止;3. 此方法适用于组织架构、产品物料清单、评论嵌套、文件系统、供应链追溯等多种场景,能灵活应对未知层级深度的数据关系,避免冗长join语句,提升查询效率与可维护性。

SQL递归查询实战 WITH子句实现树形结构遍历

SQL递归查询,尤其是借助WITH子句(Common Table Expressions, CTEs)来实现树形结构遍历,简单来说,就是一种在关系型数据库中优雅地处理层级数据的强大工具。它能让你轻松地向上追溯(比如找老板的老板)或向下展开(比如找下属的下属),而不需要写一堆复杂的嵌套查询或多次连接。它就像是给数据库装上了“层级导航”功能,让那些原本看起来复杂得一塌糊涂的父子关系,变得清晰可见。

SQL递归查询实战 WITH子句实现树形结构遍历

解决方案

要实现树形结构遍历,我们通常会用到WITH RECURSIVE(或者某些数据库中是WITH加上特定的语法,如SQL Server的WITH ... AS (ANCHOR UNION ALL RECURSIVE))。其核心思想是将一个查询分成两部分:一个“锚点成员”(Anchor Member)和一个“递归成员”(Recursive Member),然后用UNION ALL把它们连接起来。

举个最常见的例子:一个员工表,里面有员工ID姓名上级ID

SQL递归查询实战 WITH子句实现树形结构遍历

假设我们有这样的表结构和数据:

CREATE TABLE employees (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    manager_id INT,
    FOREIGN KEY (manager_id) REFERENCES employees(id)
);

INSERT INTO employees (id, name, manager_id) VALUES
(1, 'Alice', NULL),   -- CEO
(2, 'Bob', 1),
(3, 'Charlie', 1),
(4, 'David', 2),
(5, 'Eve', 2),
(6, 'Frank', 3),
(7, 'Grace', 4);

现在,我们想找出所有直接或间接向Alice汇报的员工:

SQL递归查询实战 WITH子句实现树形结构遍历
WITH RECURSIVE Subordinates AS (
    -- 锚点成员:从Alice开始
    SELECT
        id,
        name,
        manager_id,
        1 AS level -- 标记层级,Alice是第1级
    FROM
        employees
    WHERE
        name = 'Alice'

    UNION ALL

    -- 递归成员:找到上一级结果的下属
    SELECT
        e.id,
        e.name,
        e.manager_id,
        s.level + 1 AS level -- 层级加1
    FROM
        employees e
    INNER JOIN
        Subordinates s ON e.manager_id = s.id
)
SELECT
    id,
    name,
    manager_id,
    level
FROM
    Subordinates
ORDER BY
    level, id;

这段代码首先找到了Alice(锚点),然后不断地查找那些以上一层查询结果中的员工为manager_id的员工,直到没有新的下属被找到为止。level字段在这里非常有用,可以清晰地展示出每个员工在组织架构中的深度。

为什么传统的JOIN操作难以应对树形结构?

说实话,我第一次接触到这种需求时,下意识也想用JOIN来解决。毕竟,关系型数据库的核心就是JOIN嘛。但很快就会发现,对于深度不确定的树形结构,传统的JOIN操作会变得异常笨拙,甚至可以说束手无策。

你想啊,如果我想找到Alice的所有下属,包括下属的下属,以及下属的下属的下属……如果我只知道组织架构最多有三层,那我可能还能写出三个LEFT JOIN或者INNER JOIN。但万一组织架构有十层呢?或者,更要命的是,我根本不知道它到底有多少层?你总不能写十个甚至更多个JOIN吧?那样写出来的SQL语句会非常冗长、难以阅读和维护,而且性能也会是个大问题。每增加一层深度,你就得增加一个JOIN,这根本不符合“一次编写,通用执行”的编程原则。这种“预知深度”的限制,让传统JOIN在处理这类问题时显得力不从心。它更适合处理固定、明确的关系,而不是这种动态、可变深度的层级关系。

WITH RECURSIVE子句的工作原理与关键组成部分

WITH RECURSIVE子句,或者说递归CTE,它能优雅地解决传统JOIN的困境,这玩意儿的工作原理其实有点像我们编程里的递归函数调用。它主要由两部分组成,缺一不可:

  1. 锚点成员(Anchor Member): 这是递归的“起点”或“基础案例”。它是一个非递归的SELECT语句,用于生成递归的初始行集。在上面员工的例子中,就是SELECT ... FROM employees WHERE name = 'Alice'这部分。它定义了我们从哪里开始遍历树。如果没有锚点,递归就无从谈起。

    Beautiful.ai
    Beautiful.ai

    AI在线创建幻灯片

    下载
  2. 递归成员(Recursive Member): 这是递归的“迭代步骤”。它是一个SELECT语句,必须引用CTE本身(也就是Subordinates这个名字)。每次执行时,它会使用前一次迭代(包括锚点成员的第一次迭代)的结果集作为输入,然后生成新的行集。在我们的例子中,SELECT e.id, ... FROM employees e INNER JOIN Subordinates s ON e.manager_id = s.id就是递归成员。它不断地从前一轮的结果中找出新的相关数据。

这两部分通过UNION ALL连接起来。数据库系统会先执行锚点成员,得到第一批结果。然后,它会将这批结果传递给递归成员,递归成员处理后生成新的结果集。接着,这个新的结果集又会被传回给递归成员,如此反复,直到递归成员不再产生新的行为止。这就是递归的“终止条件”——当某次迭代的结果集为空时,整个递归过程就停止了。

值得注意的是,使用UNION ALL而不是UNION通常是更优的选择,因为它避免了去重操作,在大多数递归场景下,我们通常不关心中间结果的重复,而且UNION ALL的性能会更好。当然,如果你的数据中存在循环引用(比如A是B的上级,B又是A的上级),那么递归查询可能会陷入无限循环。一些数据库提供了额外的机制(如SQL Server的MAXRECURSION选项,或者PostgreSQL/Oracle的CYCLE子句)来检测和处理这种情况,防止资源耗尽。

实际应用场景:除了组织架构,还能用在哪里?

WITH RECURSIVE的魅力远不止于组织架构图。它的应用范围非常广泛,只要数据存在层级关系,它就能派上用场。我个人觉得,它简直是处理各种“父子孙”关系的利器。

  1. 产品物料清单(Bill of Materials, BOM): 想象一个复杂的产品,它由多个子部件组成,而每个子部件又可能由更小的零件构成。递归查询可以轻松地展开整个物料清单,计算每个最终产品的总零件数,或者找出某个特定零件被哪些产品直接或间接使用。这对于生产计划和成本核算来说简直是福音。

  2. 评论/论坛帖子嵌套: 很多论坛或博客的评论系统都支持回复功能,形成多级嵌套的评论串。使用递归查询,你可以轻松地将这些评论按照层级关系展示出来,甚至可以限制显示深度,或者找出某个评论的所有子评论。

  3. 文件系统结构: 数据库中存储的文件和目录信息,天然就是一种树形结构。递归查询可以用来模拟ls -Rdir /s命令,列出某个目录下所有子目录和文件,或者找出特定类型的文件。

  4. 供应链追溯: 在复杂的供应链中,产品从原材料到最终消费者可能经过多个环节。递归查询可以帮助你追溯某个批次产品的上游供应商,或者下游销售渠道,这对于质量控制和召回管理至关重要。

  5. 网络图/路径查找(简化版): 虽然专业的图数据库更适合复杂的图算法,但在关系型数据库中,对于简单的节点间连接(比如朋友关系,或者城市间航线),递归查询可以用来找出两个节点之间的所有可能路径,或者某个节点可达的所有节点。当然,这通常需要一些额外的技巧来避免循环和记录路径。

可以说,任何你看到数据呈现出“包含”、“属于”、“是...的子集”这种层级关系的场景,WITH RECURSIVE都值得你考虑。它让数据库在处理这类问题时,变得异常灵活和强大。

相关专题

更多
数据分析工具有哪些
数据分析工具有哪些

数据分析工具有Excel、SQL、Python、R、Tableau、Power BI、SAS、SPSS和MATLAB等。详细介绍:1、Excel,具有强大的计算和数据处理功能;2、SQL,可以进行数据查询、过滤、排序、聚合等操作;3、Python,拥有丰富的数据分析库;4、R,拥有丰富的统计分析库和图形库;5、Tableau,提供了直观易用的用户界面等等。

686

2023.10.12

SQL中distinct的用法
SQL中distinct的用法

SQL中distinct的语法是“SELECT DISTINCT column1, column2,...,FROM table_name;”。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

325

2023.10.27

SQL中months_between使用方法
SQL中months_between使用方法

在SQL中,MONTHS_BETWEEN 是一个常见的函数,用于计算两个日期之间的月份差。想了解更多SQL的相关内容,可以阅读本专题下面的文章。

348

2024.02.23

SQL出现5120错误解决方法
SQL出现5120错误解决方法

SQL Server错误5120是由于没有足够的权限来访问或操作指定的数据库或文件引起的。想了解更多sql错误的相关内容,可以阅读本专题下面的文章。

1159

2024.03.06

sql procedure语法错误解决方法
sql procedure语法错误解决方法

sql procedure语法错误解决办法:1、仔细检查错误消息;2、检查语法规则;3、检查括号和引号;4、检查变量和参数;5、检查关键字和函数;6、逐步调试;7、参考文档和示例。想了解更多语法错误的相关内容,可以阅读本专题下面的文章。

359

2024.03.06

oracle数据库运行sql方法
oracle数据库运行sql方法

运行sql步骤包括:打开sql plus工具并连接到数据库。在提示符下输入sql语句。按enter键运行该语句。查看结果,错误消息或退出sql plus。想了解更多oracle数据库的相关内容,可以阅读本专题下面的文章。

758

2024.04.07

sql中where的含义
sql中where的含义

sql中where子句用于从表中过滤数据,它基于指定条件选择特定的行。想了解更多where的相关内容,可以阅读本专题下面的文章。

577

2024.04.29

sql中删除表的语句是什么
sql中删除表的语句是什么

sql中用于删除表的语句是drop table。语法为drop table table_name;该语句将永久删除指定表的表和数据。想了解更多sql的相关内容,可以阅读本专题下面的文章。

420

2024.04.29

c++ 根号
c++ 根号

本专题整合了c++根号相关教程,阅读专题下面的文章了解更多详细内容。

52

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
SQL 教程
SQL 教程

共61课时 | 3.6万人学习

Java 教程
Java 教程

共578课时 | 50.9万人学习

oracle知识库
oracle知识库

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号