HierarchyID:获取父项列表的所有后代

问题描述 投票:6回答:4

我有一个像这个100, 110, 120, 130的父ID列表,它是动态的,可以改变。我希望在一组中获得指定父项的所有后代。为了获得单亲的孩子,我使用了这样的查询:

WITH parent AS (
    SELECT PersonHierarchyID FROM PersonHierarchy
    WHERE PersonID = 100    
)
SELECT * FROM PersonHierarchy
WHERE PersonHierarchyID.IsDescendantOf((SELECT * FROM parent)) = 1

不知道如何为多个父母这样做。我的第一次尝试是写几个工会,但我确信应该有更聪明的方法来做这件事。

SELECT * FROM PersonHierarchy 
WHERE PersonHierarchyID.IsDescendantOf(
    (SELECT PersonHierarchyID FROM PersonHierarchy WHERE PersonID = 100)
) = 1
UNION ALL
SELECT * FROM PersonHierarchy 
WHERE PersonHierarchyID.IsDescendantOf(
    (SELECT PersonHierarchyID FROM PersonHierarchy WHERE PersonID = 110)
) = 1
UNION ALL ...

附:我也找到了这样的查询来选择可能有用的id列表:

SELECT * FROM (VALUES (100), (110), (120), (130)) AS Parent(ParentID)

总而言之,我的目标是编写一个接受父ID数组作为参数的查询,并将所有后代返回到一个集合中。

sql-server tsql hierarchyid
4个回答
15
投票

你的想法太难了。

WITH parent AS (
    SELECT PersonHierarchyID FROM PersonHierarchy
    WHERE PersonID in (<list of parents>)    
)
SELECT * FROM PersonHierarchy
WHERE PersonHierarchyID.IsDescendantOf((SELECT * FROM parent)) = 1

不过我会这样写的:

select child.*
from PersonHierarchy as parent
inner join PersonHierarchy as child
   on child.PersonHierarchyID.IsDescendantOf(
       parent.PersonHierarchyId
   ) = 1
where Parent.PersonId in (<list of parents>)

注意:在这两种情况下,这可能会很慢,因为它必须为n * m个条目评估IsDescendantOf(其中n是父项列表的基数,m是表的基数)。

我最近有一个类似的问题,我通过编写一个表值函数来解决它,给定一个hierarchyId将返回所有父项。让我们看一下使用这种方法解决问题的方法。一,功能:

CREATE FUNCTION [dbo].[GetAllAncestors] (@h HierarchyId, @IncludeSelf bit)
RETURNS TABLE
AS RETURN

    WITH cte AS (
        SELECT @h AS h, 1 AS IncludeSelf
    )
    SELECT @h.GetAncestor(n.NumberId) AS Hierarchy
    FROM ref.Number AS n
    WHERE n.NumberId <= @h.GetLevel()
    AND n.NumberId >= 1

    UNION ALL

    SELECT h
    FROM cte
    WHERE IncludeSelf = @IncludeSelf

它假定您有一个Numbers表。它们非常有用。如果你没有,请查看接受的答案here。让我们来谈谈这个功能。从本质上讲,它表示“对于传入的hierarchyId,获取当前级别。然后调用GetAncestor直到您处于层次结构的顶部。”请注意,它可选地返回传入的hierarchyId。在我的情况下,我想考虑一个记录本身的祖先。你可能想要也可能不想做。

转到使用它的解决方案,我们得到类似的东西:

select child.*
from PersonHierarchy as child
cross apply [dbo].[GetAllAncestors](child.PersonHierarchyId, 0) as ancestors
inner join PersonHierarchy as parent
  on parent.PersonHierarchyId = ancestors.Hierarchy
where parent.PersonId in (<list of parents>)

它可能适用于您,也可能不适合您。试试看吧!


2
投票

它可能对某人有用。我通过自联接查询找到了这样做的方法:

SELECT p2.* FROM PersonHierarchy p1
LEFT JOIN PersonHierarchy p2 
    ON p2.PersonHierarchyID.IsDescendantOf(p1.PersonHierarchyID) = 1
WHERE 
    p1.PersonID IN (100, 110, 120, 130)

1
投票

您可以使用此查询

Select 
   child.*, 
   child.[PersonHierarchyID].GetLevel(),
   child.[PersonHierarchyID].GetAncestor(1)
From 
   PersonHierarchy as parents
   Inner Join PersonHierarchy as child
               On child.[PersonHierarchyID].IsDescendantOf(parents.[PersonHierarchyID] ) = 1
Where
   parents.[PersonHierarchyID] = 0x68

0
投票

请检查,这应该适合你。我还没有尝试修改你的脚本,只是把查询放在循环中。希望能帮助到你。

DECLARE @String VARCHAR(MAX) = '100, 110, 120, 130'
DECLARE @SQL VARCHAR(MAX)

SET @String = REPLACE(@String, CHAR(32), '') + ','

WHILE CHARINDEX(',', @String) > 0
    BEGIN
       DECLARE @ToString INT
       DECLARE @StringLength INT
       DECLARE @WorkingString VARCHAR(MAX)
       DECLARE @WorkingLength INT

       SET @ToString = CHARINDEX(',', @String)
       SET @StringLength = LEN(@String)
       SET @WorkingString = SUBSTRING(@String, 1, @ToString - 1)

       SET @String = SUBSTRING(@String, @ToString + 1, @StringLength)

       SET @WorkingString =  'SELECT * FROM PersonHierarchy ' + CHAR(13) + CHAR(10) 
                       + 'WHERE PersonHierarchyID.IsDescendantOf((SELECT PersonHierarchyID FROM PersonHierarchy WHERE PersonID = ' 
                       + @WorkingString + ')) = 1' + CHAR(13) + CHAR(10) 
                       + CASE WHEN CHARINDEX(',', @String) > 0 THEN 'UNION ALL'+ CHAR(13) + CHAR(10) ELSE '' END
       SET @SQL = ISNULL(@SQL,'') + @WorkingString
    END
PRINT @SQL
EXEC (@SQL)
© www.soinside.com 2019 - 2024. All rights reserved.