06. 父子節點(樹)遍歷寫法小結

對於樹/圖的遍歷,一般有2種算法來實現:迭代(Iteration)和遞歸(Recursion),迭代是利用循環反覆取值/賦值的過程;遞歸則是反覆本身調用本身來得到最終結果。
SQL Server裏的遞歸有32層嵌套限制,目的在於防止代碼進入死循環,除非使用提示OPTION (MAXRECURSION 0)。算法

測試數據:sql

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來逐層查找spa

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) 自定義函數遞歸scala

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遞歸code

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的全部父節點,返回結果以下:blog

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) 自定義函數遞歸ci

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/

相關文章
相關標籤/搜索