對於樹/圖的遍歷,一般有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/