HierarchyID: получить все потомки для списка родителей

у меня есть список идентификаторов родителя такой 100, 110, 120, 130 который является динамическим и может меняться. Я хочу получить всех потомков для указанных родителей в одном наборе. Чтобы получить детей для одного родителя, я использовал такой запрос:

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 ...

P. S. Также я нашел такой запрос, чтобы выбрать список идентификаторов, которые может быть полезно:

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

подводя итог, моя цель-написать запрос, который принимает массив родительских идентификаторов в качестве параметра и возвращает всех их потомков в одном наборе.

3 ответов


ты слишком много думаешь.

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.IsDescendantOf(parent.PersonHierarchyId) = 1
where Parent.PersonId in (<list of parents>)

Примечание: в обоих случаях это может быть медленным, так как он должен оценивать IsDescendantOf для n*m записей (с 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

предполагается, что у вас есть таблица чисел. Они очень полезны. Если у вас его нет, посмотрите на принятый ответ здесь. Давайте поговорим об этой функции на секунду. По сути, в нем говорится: "для пройденного в 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>)

Он может или не может работать для вас. Попробуйте и увидите!


Это может быть полезно для кого-то. Я нашел способ сделать это, самостоятельно присоединившись к запросу:

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

пожалуйста, проверьте, это должно работать для вас. Я не пытался изменить ваш скрипт, но просто поместил запрос в цикл. Надеюсь, это поможет.

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)