news 2026/4/3 0:06:43

SQL Server 如何递归读取树形结构?如何实现?| 多种实现方案供你选择!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SQL Server 如何递归读取树形结构?如何实现?| 多种实现方案供你选择!

🏆本文收录于 《全栈 Bug 调优(实战版)》 专栏。专栏聚焦真实项目中的各类疑难 Bug,从成因剖析 → 排查路径 → 解决方案 → 预防优化全链路拆解,形成一套可复用、可沉淀的实战知识体系。无论你是初入职场的开发者,还是负责复杂项目的资深工程师,都可以在这里构建一套属于自己的「问题诊断与性能调优」方法论,助你稳步进阶、放大技术价值 。

📌特别说明:
文中问题案例来源于真实生产环境与公开技术社区,并结合多位一线资深工程师与架构师的长期实践经验,经过人工筛选与AI系统化智能整理后输出。文中的解决方案并非唯一“标准答案”,而是兼顾可行性、可复现性与思路启发性的实践参考,供你在实际项目中灵活运用与演进。

欢迎订阅本专栏,一次订阅后,专栏内所有文章可永久免费阅读,后续更新内容皆不用再次订阅,持续更新中。

📢 问题描述

详细问题描述如下:SQL Server 如何递归读取树形结构,如何递归读取树形结构?

全文目录:

    • 📢 问题描述
    • 📣 请知悉:如下方案不保证一定适配你的问题!
      • ✅️问题理解
      • ✅️问题解决方案
        • 🟢方案 A:递归 CTE(最常用、最标准、可读性强)✅
          • A1. 查询某节点的“全部子孙(子树)”
          • A2. 查询某节点的“全部祖先(向上递归)”
          • A3. 防止“成环数据”导致死递归(强烈建议加)🧯
          • A4. 常见性能优化(真实项目非常重要)⚙️
        • 🟡方案 B:使用 `hierarchyid` 类型(查询祖先/子孙更强、索引更友好)✅
        • 🔴方案 C:Nested Sets(左右值 lft/rgt)(读子树极快,但更新成本高)
      • ✅️问题延伸
        • 1) 常见输出需求:直接生成“树形缩进显示”🌲
        • 2) 同时返回“每个节点的子节点数量 / 子树大小”
        • 3) 分页加载(懒加载树)
        • 4) 权限/组织路径判断(是否在某部门子树内)
      • ✅️问题预测(你很可能接下来会遇到的坑)🧠
      • ✅️小结
    • 🌹 结语 & 互动说明
    • 🧧 文末福利:技术成长加速包 🧧
    • 🫵 Who am I?

📣 请知悉:如下方案不保证一定适配你的问题!

如下是针对上述问题进行专业角度剖析答疑,不喜勿喷,仅供参考:

✅️问题理解

你说的“SQL Server 递归读取树形结构”,通常指一张表用父子关系存储树(邻接表模型 / Adjacency List),结构类似:

  • Id:节点ID
  • ParentId:父节点ID(根节点为NULL或 0)
  • Name:节点名称
  • 其他业务字段…

你想实现的“递归读取”一般包含这些典型需求(我会一并覆盖)✅:

  1. 从某个节点出发,取它的全部子孙(子树)
  2. 从某个节点出发,取它的全部祖先(路径到根)
  3. 输出层级Level、路径Path、并能按树形顺序排序
  4. 避免死循环(脏数据:父子成环)
  5. 性能优化:索引、递归层数限制、替代建模方式(hierarchyid、Nested Sets 等)

✅️问题解决方案

🟢方案 A:递归 CTE(最常用、最标准、可读性强)✅

适用:绝大多数 SQL Server 树查询场景(一次性查询子树/祖先/层级输出)
核心:WITH cte AS (anchor UNION ALL recursive)

A1. 查询某节点的“全部子孙(子树)”

表结构示例:

CREATETABLEdbo.Category(IdINTNOTNULLPRIMARYKEY,ParentIdINTNULL,Name NVARCHAR(100)NOTNULL);CREATEINDEXIX_Category_ParentIdONdbo.Category(ParentId);

查询:从 @RootId 开始取整个子树

DECLARE@RootIdINT=1;WITHTreeAS(-- ① Anchor:根节点SELECTId,ParentId,Name,CAST(0ASINT)AS[Level],CAST(RIGHT('0000000000'+CAST(IdASVARCHAR(10)),10)ASVARCHAR(MAX))ASSortPath,CAST('/'+CAST(IdASVARCHAR(20))+'/'ASVARCHAR(MAX))ASNodePathFROMdbo.CategoryWHEREId=@RootIdUNIONALL-- ② Recursive:不断找子节点SELECTc.Id,c.ParentId,c.Name,t.[Level]+1,t.SortPath+'.'+RIGHT('0000000000'+CAST(c.IdASVARCHAR(10)),10),t.NodePath+CAST(c.IdASVARCHAR(20))+'/'FROMdbo.Category cINNERJOINTree tONc.ParentId=t.Id)SELECTId,ParentId,Name,[Level],NodePathFROMTreeORDERBYSortPathOPTION(MAXRECURSION32767);

要点解释(很关键)

  • Level:树深度(根=0)
  • SortPath:用补零的ID拼接出可排序路径,ORDER BY SortPath能得到“树形展开”的顺序
  • NodePath:路径字符串(可用于调试、查环、权限路径判断等)
  • MAXRECURSION:默认 100 层,树很深会报错;你可设更大或 0(无限,但不推荐无限,容易被坏数据拖死)
A2. 查询某节点的“全部祖先(向上递归)”
DECLARE@NodeIdINT=9;WITHParentsAS(SELECTId,ParentId,Name,CAST(0ASINT)AS[Level],CAST('/'+CAST(IdASVARCHAR(20))+'/'ASVARCHAR(MAX))ASNodePathFROMdbo.CategoryWHEREId=@NodeIdUNIONALLSELECTp.Id,p.ParentId,p.Name,c.[Level]+1,CAST('/'+CAST(p.IdASVARCHAR(20))+c.NodePathASVARCHAR(MAX))-- 祖先在前FROMdbo.Category pINNERJOINParents cONc.ParentId=p.Id)SELECT*FROMParentsORDERBY[Level]DESCOPTION(MAXRECURSION32767);
A3. 防止“成环数据”导致死递归(强烈建议加)🧯

脏数据例子:A 的父是 B,B 的父又是 A。递归会无限。

在递归部分加“路径去重”即可避免:

DECLARE@RootIdINT=1;WITHTreeAS(SELECTId,ParentId,Name,CAST('/'+CAST(IdASVARCHAR(20))+'/'ASVARCHAR(MAX))ASNodePath,CAST(0ASINT)AS[Level]FROMdbo.CategoryWHEREId=@RootIdUNIONALLSELECTc.Id,c.ParentId,c.Name,t.NodePath+CAST(c.IdASVARCHAR(20))+'/',t.[Level]+1FROMdbo.Category cINNERJOINTree tONc.ParentId=t.IdWHEREt.NodePathNOTLIKE'%/'+CAST(c.IdASVARCHAR(20))+'/%'-- ✅ 防环)SELECT*FROMTreeOPTION(MAXRECURSION32767);

这样就算表里有环,也不会无限递归;并且你还能通过NodePath定位出异常链路。👍

A4. 常见性能优化(真实项目非常重要)⚙️
  1. 必须要有索引ParentId上建非聚集索引

    CREATEINDEXIX_Category_ParentIdONdbo.Category(ParentId);
  2. 树查询频繁、数据量大时:

    • 只取必要字段(减少 IO)
    • 限制深度:WHERE t.Level < 50(防止异常数据拖死)
    • 根节点过滤尽量精确(缩小 anchor 结果集)
  3. 结果需要大量排序时:SortPath可能较长,注意字符串排序开销;更高阶做法见 🟡方案 B/C

🟡方案 B:使用hierarchyid类型(查询祖先/子孙更强、索引更友好)✅

适用:

  • 你经常要查“某节点的整个子树/祖先链”,并且关注性能
  • 树结构比较稳定或你能接受维护hierarchyid字段

表结构示例:

CREATETABLEdbo.CategoryH(IdINTNOTNULLPRIMARYKEY,Name NVARCHAR(100)NOTNULL,Hid HIERARCHYIDNOTNULL);CREATEINDEXIX_CategoryH_HidONdbo.CategoryH(Hid);

查询:取某节点全部子孙

DECLARE@IdINT=1;DECLARE@RootHidHIERARCHYID=(SELECTHidFROMdbo.CategoryHWHEREId=@Id);SELECT*FROMdbo.CategoryHWHEREHid.IsDescendantOf(@RootHid)=1ORDERBYHid;

查询:取祖先链(含自己)

DECLARE@IdINT=9;DECLARE@HHIERARCHYID=(SELECTHidFROMdbo.CategoryHWHEREId=@Id);SELECT*FROMdbo.CategoryHWHERE@H.IsDescendantOf(Hid)=1ORDERBYHid;

优势(真香点)✨

  • IsDescendantOf()GetAncestor()GetLevel()非常适配树
  • Hid可建索引,子树查询通常比纯 CTE 更稳定更快
  • 排序天然按层级路径排序(ORDER BY Hid

代价/注意点

  • 你需要在插入节点时计算Hid(例如用父节点GetDescendant()生成)
  • 移动子树(更换父节点)会涉及Hid批量更新(但通常仍可控)
🔴方案 C:Nested Sets(左右值 lft/rgt)(读子树极快,但更新成本高)

适用:

  • “读远多于写”的层级数据(如:类目树、组织架构不常变)
  • 你需要极快地取整棵子树(不用递归、纯范围查询)

思想:每个节点维护一对区间[lft, rgt],子树就是一个范围。
查询子树变成:

SELECTchild.*FROMCategoryNSASparentJOINCategoryNSASchildONchild.lftBETWEENparent.lftANDparent.rgtWHEREparent.Id=@RootIdORDERBYchild.lft;

优势:读性能非常强、简单稳定
劣势:插入/移动节点要重算大量lft/rgt,维护成本高

✅️问题延伸

1) 常见输出需求:直接生成“树形缩进显示”🌲

你可以在最终 SELECT 做缩进:

SELECTREPLICATE(N' ',[Level])+NameASTreeText,Id,ParentId,[Level]FROMTreeORDERBYSortPath;
2) 同时返回“每个节点的子节点数量 / 子树大小”

你可以在 CTE 结果上再聚合(或用辅助表缓存 subtree_size)。

3) 分页加载(懒加载树)

前端树组件(Ant Design / Element)常用:

  • 初次只取根的一级子节点
  • 展开时按ParentId = ?再查一层(不用递归)
    递归查询更多用于:导出、全量计算、权限匹配等。
4) 权限/组织路径判断(是否在某部门子树内)
  • CTENodePath
  • hierarchyidIsDescendantOf()(更优雅)

✅️问题预测(你很可能接下来会遇到的坑)🧠

  1. 递归层数超过 100 报错
  • 解决:OPTION (MAXRECURSION n)
  • 但更重要:检查是否有环、是否有异常深度数据
  1. 性能抖动:数据量大 + 深层树 + 字符串排序
  • 优先:ParentId索引
  • 进一步:考虑hierarchyid或 Nested Sets
  • 如果你每次都要全树:考虑“物化路径/闭包表 Closure Table”做缓存
  1. 数据成环导致无限递归/超时
  • 强烈建议:递归时做NodePath防环
  • 另:加约束/触发器,在写入时禁止环(更彻底)
  1. 树节点移动(换父节点)带来维护问题
  • 邻接表:移动很简单(更新 ParentId),但查询递归成本存在
  • hierarchyid / nested sets:移动要维护额外字段(可换来读取性能)

✅️小结

  • 最推荐:方案A递归 CTE(邻接表)——上手最快、兼容最好,配合ParentId索引 +MAXRECURSION+ 防环路径,能覆盖 90% 场景 ✅
  • 更高性能/更树友好:方案Bhierarchyid—— 子树/祖先查询非常舒服且可索引,适合树查询频繁的系统
  • 读多写少且极致读取:方案C Nested Sets —— 读子树像查范围一样快,但维护成本高

🌹 结语 & 互动说明

希望以上分析与解决思路,能为你当前的问题提供一些有效线索或直接可用的操作路径

若你按文中步骤执行后仍未解决:

  • 不必焦虑或抱怨,这很常见——复杂问题往往由多重因素叠加引起;
  • 欢迎你将最新报错信息、关键代码片段、环境说明等补充到评论区;
  • 我会在力所能及的范围内,结合大家的反馈一起帮你继续定位 👀

💡如果你有更优或更通用的解法:

  • 非常欢迎在评论区分享你的实践经验或改进方案;
  • 你的这份补充,可能正好帮到更多正在被类似问题困扰的同学;
  • 正所谓「赠人玫瑰,手有余香」,也算是为技术社区持续注入正向循环

🧧 文末福利:技术成长加速包 🧧

文中部分问题来自本人项目实践,部分来自读者反馈与公开社区案例,也有少量经由全网社区与智能问答平台整理而来。

若你尝试后仍没完全解决问题,还请多一点理解、少一点苛责——技术问题本就复杂多变,没有任何人能给出对所有场景都 100% 套用的方案。

如果你已经找到更适合自己项目现场的做法,非常建议你沉淀成文档或教程,这不仅是对他人的帮助,更是对自己认知的再升级。

如果你还在持续查 Bug、找方案,可以顺便逛逛我专门整理的 Bug 专栏👉《全栈 Bug 调优(实战版)》👈️

这里收录的都是在真实场景中踩过的坑,希望能帮你少走弯路,节省更多宝贵时间。

✍️如果这篇文章对你有一点点帮助:

  • 欢迎给 bug菌 来个一键三连:关注 + 点赞 + 收藏
  • 你的支持,是我持续输出高质量实战内容的最大动力。

同时也欢迎关注我的硬核公众号 「猿圈奇妙屋」:

获取第一时间更新的技术干货、BAT 等互联网公司最新面试真题、4000G+ 技术 PDF 电子书、简历 / PPT 模板、技术文章 Markdown 模板等资料,通通免费领取
你能想到的绝大部分学习资料,我都尽量帮你准备齐全,剩下的只需要你愿意迈出那一步来拿。

🫵 Who am I?

我是 bug菌:

  • 热活跃于 CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等技术社区;
  • CSDN 博客之星 Top30、华为云多年度十佳博主/卓越贡献者、掘金多年度人气作者 Top40;
  • 掘金、InfoQ、51CTO 等平台签约及优质作者;
  • 全网粉丝累计30w+

更多高质量技术内容及成长资料,可查看这个合集入口 👉 点击查看 👈️

硬核技术公众号「猿圈奇妙屋」期待你的加入,一起进阶、一起打怪升级。

- End -

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/15 16:09:20

亲测好用 8个AI论文网站:研究生毕业论文写作与格式规范全测评

在当前学术研究日益数字化的背景下&#xff0c;研究生群体面临着论文写作、文献检索、格式规范等多重挑战。尤其是在AI技术广泛应用的今天&#xff0c;如何选择一款高效、专业且符合学术规范的写作工具&#xff0c;成为许多学生关注的焦点。为了帮助广大研究生更科学地挑选适合…

作者头像 李华
网站建设 2026/3/28 18:27:24

Deepoc数学大模型:赋能半导体产业,破解研发与量产核心痛点

半导体工艺向3nm及以下先进节点迭代过程中&#xff0c;芯片设计复杂度、制造精度呈几何级攀升&#xff0c;研发周期冗长、试错成本高企、高端EDA工具掣肘、量产良率难管控等行业痛点日益凸显&#xff0c;严重制约产业高质量发展。传统半导体产业以工程师经验为核心驱动&#xf…

作者头像 李华
网站建设 2026/4/1 1:56:51

计算机毕业设计springboot大学生社会实践信息管理系统 基于SpringBoot的高校社会实践活动全周期管理平台 基于SpringBoot的大学生校外实践教学信息化服务平台

计算机毕业设计springboot大学生社会实践信息管理系统0zank987 &#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。在高等教育深化改革的背景下&#xff0c;社会实践活动已成为培养学…

作者头像 李华
网站建设 2026/4/1 12:01:55

LangChain十年演进

LangChain 的十年&#xff08;2015–2025&#xff09;&#xff0c;是一段从“不存在”到“爆发式流行”&#xff0c;再到“通过 LangGraph 深度重塑 Agent 逻辑”&#xff0c;最后在 2025 年演进为“云原生、内核级编排的 AI 操作系统驱动程序”的激荡史。 虽然 LangChain 诞生…

作者头像 李华
网站建设 2026/3/15 20:04:49

好写作AI:再也不用“好像/可能/大概”了!学术语言变身指南

导语&#xff1a;每个深夜的文档前&#xff0c;都有个被表达“榨干”的灵魂 写论文时&#xff0c;你是否经历过这样的“精神分裂”&#xff1f;脑海里思如泉涌&#xff0c;指尖输出的文字却像“小学生作文”&#xff1b;明明数据扎实、观点新颖&#xff0c;写出来却总觉得“差…

作者头像 李华