06. 父子节点(树)遍历写法小结

对于树/图的遍历,通常有2种算法来实现:迭代(Iteration)和递归(Recursion),迭代是利用循环反复取值/赋值的过程;递归则是反复自己调用自己来获得最终结果。
SQL Server里的递归有32层嵌套限制,目的在于防止代码进入死循环,除非使用提示OPTION (MAXRECURSION 0)。

测试数据:

复制代码
if OBJECT_ID('city') is not null
    drop table city
GO
create table city
(
id    int,
name  nvarchar(10),
pid   int,
depth int
)
GO
insert into city 
select  1,N'江苏省',0,0 union all
select  2,N'南京市',1,1 union all
select  3,N'玄武区',2,2 union all
select  4,N'鼓楼区',2,2 union all
select  5,N'浙江省',0,0 union all
select  6,N'杭州市',5,1 union all
select  7,N'西湖区',6,2 union all
select  8,N'滨江区',6,2 union all
select  9,N'苏州市',1,1 union all
select 10,N'吴中区',9,2 union all
select 11,N'吴江区',9,2
复制代码

一. 查找子节点
查找节点1的所有子节点,返回结果如下:

id name pid depth
1 江苏省 0 0
2 南京市 1 1
3 玄武区 2 2
4 鼓楼区 2 2
9 苏州市 1 1
10 吴中区 9 2
11 吴江区 9 2

1. 迭代
(1) 不借助depth,通过not in来向下查找

复制代码
if OBJECT_ID('f_get_child') is not null
    drop function f_get_child
GO
create function f_get_child
(
@id int
)
returns @t table(id int)
as
begin
    insert into @t select @id
    --insert into @t select id from city where pid = @id
    while @@ROWCOUNT>0
    begin
        insert into @t 
        select a.id 
        from city a inner join @t b on a.pid = b.id
        where a.id not in(select id from @t)
    end
    return
end
GO
select * from city where id in(select id from f_get_child(1))
复制代码

(2) 通过depth来逐层查找

复制代码
if OBJECT_ID('f_get_child') is not null
    drop function f_get_child
GO
create function f_get_child
(
@id int
)
returns @t table(id int, depth int)
begin
    declare @depth int
    set @depth = 0
    insert @t select ID,@depth from city where ID =@ID
    while @@ROWCOUNT>0
    begin
        set @depth = @depth + 1
        insert @t select a.ID,@depth
          from city a, @t b
         where a.pid = b.ID
           and b.depth = @depth - 1
    end    
    return      
end
GO
select * from city where id in(select id from f_get_child(1))
复制代码

2. 递归
(1) 自定义函数递归

复制代码
if OBJECT_ID('f_get_child') is not null
    drop function f_get_child
GO
create function f_get_child
(
@id int
)
returns @t table(id int)
as
begin
    declare @pid int
    set @pid = null
    insert into @t
    select @id 
    union all 
    select id from city where pid = @id
    
    if exists(select 1
        from city a inner join @t b on a.pid = b.id
        where a.id not in(select id from @t))
    begin
        insert into @t 
        select a.id 
        from city a inner join @t b on a.pid = b.id
        where a.id not in(select id from @t)
        union all
        select * from f_get_child(@pid)
    end
    return
end
GO
select * from city where id in(select * from f_get_child(1))
复制代码

(2) CTE递归

复制代码
declare @id int
set @id = 1;
with tmp
as
(
select * from city where id = @id
union all
select a.* from city a
inner join tmp b
on a.pid = b.id
)
select * from tmp order by id
复制代码

二. 查找父节点
查找节点8的所有父节点,返回结果如下:

id name pid depth
5 浙江省 0 0
6 杭州市 5 1
8 滨江区 6 2

1. 迭代
父节点只有一个,不需要做什么限制,一直往上级查找pid就可以了。

复制代码
if OBJECT_ID('f_get_parent') is not null
    drop function f_get_parent
GO
create function f_get_parent
(
@id int
)
returns @t table(id int)
as
begin
    declare @pid int
    insert into @t select @id
    select @pid = pid from city where id = @id
    while @pid<>0
    begin
        insert into @t values(@pid)
        select @pid=pid from city where id=@pid
    end
    return
end
GO
select * from city where id in(select * from f_get_parent(8))
复制代码

2. 递归
(1) 自定义函数递归

复制代码
if OBJECT_ID('f_get_parent') is not null
    drop function f_get_parent
GO
create function f_get_parent(@id int)
returns @t table(id int)
AS
begin
    declare @pid int
    select top 1 @pid = pid
    from city
    where id = @id
    if @pid <> 0
    begin
        insert into @t
        select @id 
        union all
        select * from f_get_parent(@pid)
    end
    else
    begin
        insert into @t
        select @id 
    end
    return
end
GO
select * from city where id in(select * from f_get_parent(8))
复制代码

(2) CTE递归

复制代码
declare @id int
set @id = 8;
with tmp
as
(
select * from city where id = @id
union all
select a.* from city a
inner join tmp b
on a.id = b.pid
)
select * from tmp order by id
复制代码

 

注意:(更新:09/28/2018)

之前通过递归函数写的父/子节点遍历逻辑有问题,只能遍历2层深度的节点,函数递归可参考以下链接:

Recursion in T–SQL

https://technet.microsoft.com/en-us/library/aa175801(v=sql.80).aspx

Recursive Scalar Function in T-SQL

https://stevestedman.com/2013/04/recursive-scalar-function-in-t-sql/

posted @   张骞  阅读(3975)  评论(4编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示