给定一个带有 hierarchyid
类型列的表,如何编写查询以返回特定节点的所有祖先行?
有一个 IsDescendantOf()
函数可用于获取子级,但没有对应的 IsAncestorOf()
函数来返回祖先(而缺少一个 GetAncestors()
函数似乎是一个大疏忽)。
给定一个带有 hierarchyid
类型列的表,如何编写查询以返回特定节点的所有祖先行?
有一个 IsDescendantOf()
函数可用于获取子级,但没有对应的 IsAncestorOf()
函数来返回祖先(而缺少一个 GetAncestors()
函数似乎是一个大疏忽)。
最常用的方法是使用递归公共表达式(CTE)
WITH Ancestors(Id, [Name], AncestorId) AS
(
SELECT
Id, [Name], Id.GetAncestor(1)
FROM
dbo.HierarchyTable
WHERE
Name = 'Joe Blow' -- or whatever you need to select that node
UNION ALL
SELECT
ht.Id, ht.[Name], ht.Id.GetAncestor(1)
FROM
dbo.HierarchyTable ht
INNER JOIN
Ancestors a ON ht.Id = a.AncestorId
)
SELECT *, Id.ToString() FROM Ancestors
(改编自Simon Ince的博客文章)
Simon Ince还提出了第二种方法,他基本上是反转了条件 - 而不是检测那些是目标人物的祖先条目的人员条目,他将检查结果反过来:
DECLARE @person hierarchyid
SELECT @person = Id
FROM dbo.HierachyTable
WHERE [Name] = 'Joe Blow';
SELECT
Id, Id.ToString() AS [Path],
Id.GetLevel() AS [Level],
Id.GetAncestor(1),
Name
FROM
dbo.HierarchyTable
WHERE
@person.IsDescendantOf(Id) = 1
这会选择您表中的所有行,其中您感兴趣的目标人物是任何层次结构下的一个后代。因此,它将查找该目标人物的直接和非直接祖先,一直到根。
这是一个将答案汇总到单个选择(select)中的解决方案:
SELECT t1.Id.ToString() as Path, t1.Name
FROM (SELECT * FROM HierarchyTable
WHERE Name = 'Joe Blow') t2,
HierarchyTable t1
WHERE t2.Id.IsDescendantOf(t1.Id) = 1
Declare @hid hierarchyid=0x5D10 -- Child hierarchy id
SELECT
*
FROM
dbo.TableName
WHERE
@hid.IsDescendantOf(ParentHierarchyId) = 1
我编写了一个自定义的表值函数,可以将 hierarchyid 值扩展为其组成的祖先。然后可以通过在 hierarchyid 列上进行连接来获取这些祖先。
alter function dbo.GetAllAncestors(@h hierarchyid, @ReturnSelf bit)
returns table
as return
select @h.GetAncestor(n.Number) as h
from dbo.Numbers as n
where n.Number <= @h.GetLevel()
or (@ReturnSelf = 1 and n.Number = 0)
union all
select @h
where @ReturnSelf = 1
go
select child.ID, parent.ID
from dbo.yourTable as child
cross apply dbo.GetAllAncestors(child.hid, 1) as a
join dbo.yourTable as parent
on parent.hid = a.h
完善Ben Thui的答案,我认为这是目前最好的答案...
以下方法允许在一个查询中检索不仅一个,而且可能是多个叶行及其祖先。
Create Or Alter Function dbo.GetAllAncestors
(
@Path HierarchyId,
@WithSelf Bit = 1,
@MinLevel Int = 0,
@MaxLevel Int = Null
)
Returns Table
As
Return
With Ancestor As
(
Select @Path As Path
Union All
Select Path.GetAncestor(1)
From Ancestor
Where Path.GetLevel() > 0
)
Select Path, Path.GetLevel() As Level
From Ancestor
Where (@WithSelf = 1 Or Path <> @Path)
And Path.GetLevel() >= Case When @MinLevel < 0 Or @MinLevel Is Null Then 0 Else @MinLevel End
And (@MaxLevel Is Null Or Path.GetLevel() <= @MaxLevel)
使用方法:
-- This assumes the table has a Path HierarchyId colum, and the values are unique and indexed.
-- If you know the path
Select *
From MyTable
Where Path In
(
Select Path From dbo.GetAllAncestors(@ThePath, Default, Default, Default)
)
-- If you don't know the path
Select *
From MyTable t1
Where Path In
(
Select Path
From MyTable t2
Cross Apply dbo.GetAllAncestors(t2.Path, Default, Default, Default)
Where /* Find the leaf record(s) here.
Note that if multiple rows match, they will all be returned as well as their parents in a single roundtrip. */
)
DECLARE @hid_Specific HIERARCHYID
SET @hid_Specific = '/1/1/3/1/';
SELECT hrchy_id,* FROM tblHierarchyData
WHERE PATINDEX(hrchy_id.ToString() + '%', @hid_Specific.ToString()) = 1
child.IsDescendantOf(parent)
不就是parent.IsAncestorOf(child)
吗? - GabeIsAncestorOf(node)
存在,它们将是等效的。这不需要递归查询。 - Mads Nielsen