繁体   English   中英

SQL层次结构 - 解析给定节点的所有祖先的完整路径

[英]SQL Hierarchy - Resolve full path for all ancestors of a given node

我有一个由邻接列表描述的层次结构。 没有一个根元素,但我确实有数据来识别层次结构中的叶子(终端)项目。 所以,一个看起来像这样的层次......

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
  • id 6就是它自己
  • 它的父母,身份3将具有3和6的后代
  • 它的父,id 1将具有1,3和6的后瞻

我将使用此数据在层次结构中的每个级别提供汇总计算。 假设我可以获得上面的数据集,这很有效。

我已经使用两个recusive 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来完成这个,而不是必须两次递归层次结构。

我不确定这是否表现更好,甚至在所有情况下都能产生正确的结果,但您可以捕获节点列表,然后使用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); 

好吧,这一直困扰着我,因为我已经阅读了这个问题而且我刚回来再想一想.....无论如何,为什么你需要回归以获得所有的后代? 你已经要求祖先不是后代,而你的结果集并没有试图让其他兄弟姐妹,大孩子等等。在这种情况下,它会得到一个父母和一个大父母。 你的第一个cte为你提供了你需要知道的一切,除非祖先的id也是parentid。 因此,使用联合所有,设置原始祖先的一点魔力,并且您拥有所需的一切而无需第二次递归。

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 is null 那个null是我将在一分钟内处理的一个特例。

在此输入图像描述

请记住,此时你的查询是产生Ancestors而不是descendants ,但它没有给你的是自我引用,意思是grandparent = grandparentparent = parentself = self 但是你需要做的就是为每个id添加行,并使parentid等于它们的id 因此union 现在您的结果集几乎完全形成:

在此输入图像描述

null parentid的特例。 所以null parentid标识originating ancestor意味着ancestor没有其他ancestor在你的数据集。 以下是您将如何利用这一优势。 因为你在leaf level开始你的初始递归,所以你没有直接绑定到你从originating ancestor开始的id ,但是在每个其他级别,只是劫持那个空的父id并翻转你的值,你现在有了你的叶子的祖先。

在此输入图像描述

然后最后如果你想让它成为一个后代表切换列,你就完成了。 最后一个注意事项DISTINCT是存在的,以防id重复另一个parentid 例如6 | 3 6 | 3和另一个6 | 4记录 6 | 4

在此输入图像描述

因为您的数据是树结构,所以我们可以使用hierarchyid数据类型来满足您的需求(尽管您说不能在评论中)。 首先,简单的部分 - 使用递归cte生成hierarchyid

with 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而不是持久化它的很多好处(你要么必须在边桌中保持最新,要么每次都生成它)。 但是你去吧。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM