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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM