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)