我有一个由邻接列表描述的层次结构。没有一个根元素,但我确实有数据来识别层次结构中的叶子(终端)项目。所以,一个看起来像这样的层次......
1
- 2
- - 4
- - - 7
- 3
- - 5
- - 6
8
- 9
...将由表格描述,就像这样。 注意:我无法更改此格式。
id parentid isleaf
--- -------- ------
1 null 0
2 1 0
3 1 0
4 2 0
5 3 1
6 3 1
7 4 1
8 null 0
9 8 1
这是示例表定义和数据:
CREATE TABLE [dbo].[HiearchyTest](
[id] [int] NOT NULL,
[parentid] [int] NULL,
[isleaf] [bit] NOT NULL
)
GO
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (1, NULL, 0)
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (2, 1, 0)
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (3, 1, 0)
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (4, 2, 0)
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (5, 3, 1)
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (6, 3, 1)
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (7, 4, 1)
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (8, NULL, 0)
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (9, 8, 1)
GO
由此,我需要提供任何id并获取所有祖先的列表,包括每个祖先的所有后代。所以,如果我提供 id = 6 的输入,我希望如下:
id descendentid
-- ------------
1 1
1 3
1 6
3 3
3 6
6 6
我将使用此数据在层次结构中的每个级别提供汇总计算。这很有效,假设我可以获得上面的数据集。
我使用两个重复的ctes完成了这个 - 一个用于获取hiearchy中每个节点的“terminal”项。然后,第二个我获得所选节点的完整祖先(因此,6解析为6,3,1),然后走上去获得全套。我希望我错过了一些东西,这可以在一轮中完成。以下是双递归代码示例:
declare @test int = 6;
with cte as (
-- leaf nodes
select id, parentid, id as terminalid
from HiearchyTest
where isleaf = 1
union all
-- walk up - preserve "terminal" item for all levels
select h.id, h.parentid, c.terminalid
from HiearchyTest as h
inner join
cte as c on h.id = c.parentid
)
, cte2 as (
-- get all ancestors of our test value
select id, parentid, id as descendentid
from cte
where terminalid = @test
union all
-- and walkup from each to complete the set
select h.id, h.parentid, c.descendentid
from HiearchyTest h
inner join cte2 as c on h.id = c.parentid
)
-- final selection - order by is just for readability of this example
select id, descendentid
from cte2
order by id, descendentid
其他细节:“真实”层次结构将比示例大得多。它在技术上可以具有无限深度,但实际上它很少会超过10级。
总之,我的问题是我是否可以使用单个递归cte完成此操作,而不必两次递归层次结构。
答案 0 :(得分:1)
我不确定这是否表现更好,甚至在所有情况下都能产生正确的结果,但您可以捕获节点列表,然后使用xml功能将其解析出来并交叉应用于id列表:
declare @test int = 6;
;WITH cte AS (SELECT id, parentid, CAST(id AS VARCHAR(MAX)) as IDlist
FROM HiearchyTest
WHERE isleaf = 1
UNION ALL
SELECT h.id, h.parentid , CAST(CONCAT(c.IDlist,',',h.id) AS VARCHAR(MAX))
FROM HiearchyTest as h
JOIN cte as c
ON h.id = c.parentid
)
,cte2 AS (SELECT *, CAST ('<M>' + REPLACE(IDlist, ',', '</M><M>') + '</M>' AS XML) AS Data
FROM cte
WHERE IDlist LIKE '%'+CAST(@test AS VARCHAR(50))+'%'
)
SELECT id,Split.a.value('.', 'VARCHAR(100)') AS descendentid
FROM cte2 a
CROSS APPLY Data.nodes ('/M') AS Split(a);
答案 1 :(得分:1)
好的,这一直困扰着我,因为我已经阅读了这个问题而且我刚回来再想一想.....无论如何,你为什么需要回归以获得所有的后代?你已经要求祖先不是后代,而你的结果集并没有试图让其他兄弟姐妹,大孩子等等。在这种情况下,它会得到一个父母和一个大父母。你的第一个cte为你提供了你需要知道的一切,除非祖先的id也是parentid。因此,使用union all,设置原始祖先有点神奇,并且您可以在没有第二次递归的情况下获得所需的一切。
declare @test int = 6;
with cte as (
-- leaf nodes
select id, parentid, id as terminalid
from HiearchyTest
where isleaf = 1
union all
-- walk up - preserve "terminal" item for all levels
select h.id, h.parentid, c.terminalid
from HiearchyTest as h
inner join
cte as c on h.id = c.parentid
)
, cteAncestors AS (
SELECT DISTINCT
id = IIF(parentid IS NULL, @Test, id)
,parentid = IIF(parentid IS NULL,id,parentid)
FROM
cte
WHERE
terminalid = @test
UNION
SELECT DISTINCT
id
,parentid = id
FROM
cte
WHERE
terminalid = @test
)
SELECT
id = parentid
,DecendentId = id
FROM
cteAncestors
ORDER BY
id
,DecendentId
您的第一个cte
的结果集会为您提供2 ancestors
并与他们的ancestor
自相关,但原始祖先parentid
{{1 }}。 is null
是我将在一分钟内处理的一个特例。
请记住,此时您的查询正在生成null
而不是Ancestors
,但它未提供给您的是自我引用,意思是descendants
,grandparent = grandparent
,{{1 }}。但是,要做到这一点,您只需要为每个parent = parent
添加行,并使self = self
等于其id
。因此parentid
。现在你的结果集几乎完全形成了:
id
的特例。因此,union
标识null parentid
null parentid
,表示originating
在您的数据集中没有其他ancestor
。以下是您将如何利用这一优势。因为您在ancestor
处开始了初始递归,所以与ancestor
开始的leaf level
没有直接联系,但是在其他所有级别上都只是劫持了这个空父级id并翻转周围的值,你现在有了你的叶子的祖先。
然后最后如果你想让它成为一个后代表切换列,你就完成了。如果id
与其他originating ancestor
一起重复,则会留下最后一个注释DISTINCT
。例如。 id
和parentid
答案 2 :(得分:1)
因为您的数据是树结构,我们可以使用hierarchyid数据类型来满足您的需求(尽管您说您可以在评论中)。首先,简单的部分 - 使用递归cte
生成hierarchyidwith cte as (
select id, parentid,
cast(concat('/', id, '/') as varchar(max)) as [path]
from [dbo].[HiearchyTest]
where ParentID is null
union all
select child.id, child.parentid,
cast(concat(parent.[path], child.id, '/') as varchar(max))
from [dbo].[HiearchyTest] as child
join cte as parent
on child.parentid = parent.id
)
select id, parentid, cast([path] as hierarchyid) as [path]
into h
from cte;
接下来,我写了一个小表值函数:
create function dbo.GetAllAncestors(@h hierarchyid, @ReturnSelf bit)
returns table
as return
select @h.GetAncestor(n.n) as h
from dbo.Numbers as n
where n.n <= @h.GetLevel()
or (@ReturnSelf = 1 and n.n = 0)
union all
select @h
where @ReturnSelf = 1;
有了这个,得到你想要的结果集并不是太糟糕了:
declare @h hierarchyid;
set @h = (
select path
from h
where id = 6
);
with cte as (
select *
from h
where [path].IsDescendantOf(@h) = 1
or @h.IsDescendantOf([path]) = 1
)
select h.id as parent, c.id as descendentid
from cte as c
cross apply dbo.GetAllAncestors([path], 1) as a
join h
on a.h = h.[path]
order by h.id, c.id;
当然,你错过了使用hierarchyid而不是持久化的很多好处(你要么必须在边桌中保持最新,要么每次都生成它)。但是你去了。