对于树/图的遍历,一般有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/