查找分层数据的所有祖先
Find all ancestors of hierarchical data
我有以下 table 帽子包含具有层次结构的数据结构
+----+----------+-------------+
| ID | ParentID | FullPath |
+----+----------+-------------+
| 1 | NULL | (1) |
| 2 | 1 | (1)/(2) |
| 3 | 2 | (1)/(2)/(3) |
| 4 | NULL | (4) |
| 5 | 4 | (4)/(5) |
| 6 | 4 | (4)/(6) |
| 7 | 6 | (4)/(6)/(7) |
+----+----------+-------------+
如何检索项目的祖先?
例如,如果我正在寻找 ID 3 的祖先,我会得到 1 和 2。
同样,如果我正在寻找 7,我会得到 4 和 6(注 5 不存在)。
我知道我可以通过解析列并使用动态 SQL 来避免将 CTE 与 FullPath 一起使用,但我很难创建查询。
编辑:
我想要一个查询,让我获得项目的每个祖先行。例如,如果我想要 7 的祖先,查询将 return 以下 table:
+----+----------+-------------+
| ID | ParentID | FullPath |
+----+----------+-------------+
| 4 | NULL | (4) |
| 6 | 4 | (4)/(6) |
+----+----------+-------------+
原因是因为我有更多描述该项目的列,我需要获取它们并进行比较。
如果您已经将完整路径作为数据的一部分,为什么不从中解析出祖先呢? string_split()
.
不需要动态 SQL 或 CTE
示例数据
declare @Data table
(
ID int,
ParentID int,
FullPath nvarchar(50)
);
insert into @Data (ID, ParentID, FullPath) values
(1, NULL, '(1)' ),
(2, 1 , '(1)/(2)' ),
(3, 2 , '(1)/(2)/(3)' ),
(4, NULL, '(4)' ),
(5, 4 , '(4)/(5)' ),
(6, 4 , '(4)/(6)' ),
(7, 6 , '(4)/(6)/(7)' );
解决方案
select d.ID as SelectedID,
da.*
from @Data d
cross apply string_split(d.FullPath, '/') s
join @Data da -- data ancestor
on da.ID = convert(int, replace(replace(s.value, '(', ''), ')', ''))
where d.ID = 7
and d.ID <> da.ID -- filter out ID itself
order by d.ID, da.ID;
结果
SelectedID ID ParentID FullPath
----------- ----------- ----------- -------------
7 4 NULL (4)
7 6 4 (4)/(6)
这可以使用 self-join 来完成:-
DECLARE @Hierarchy TABLE
(
ID int PRIMARY KEY
, ParentID int
-- Non-clustered index key can be up to 1600 bytes on SQL 2016+
, FullPath varchar(1028) UNIQUE
)
;
INSERT @Hierarchy
( ID, ParentID, FullPath )
VALUES
( 1, NULL, '(1)' )
, ( 2, 1 , '(1)/(2)' )
, ( 3, 2 , '(1)/(2)/(3)' )
, ( 4, NULL, '(4)' )
, ( 5, 4 , '(4)/(5)' )
, ( 6, 4 , '(4)/(6)' )
, ( 7, 6 , '(4)/(6)/(7)' )
;
SELECT N.ID
, A.ID
, A.ParentID
, A.FullPath
FROM @Hierarchy AS N
INNER JOIN @Hierarchy AS A ON N.FullPath LIKE A.FullPath + '%'
WHERE N.ID <> A.ID
ORDER BY N.ID ASC
, A.ID ASC
;
在我的测试中,上述查询确实使用了 FullPath 上的唯一索引。这是一种替代方法,但它会进行全面扫描而不是使用索引:-
SELECT N.ID
, A.ID
, A.ParentID
, A.FullPath
FROM @Hierarchy AS N
INNER JOIN @Hierarchy AS A ON SUBSTRING(N.FullPath, 1, LEN(A.FullPath)) = A.FullPath
WHERE N.ID <> A.ID
ORDER BY N.ID ASC
, A.ID ASC
;
我有以下 table 帽子包含具有层次结构的数据结构
+----+----------+-------------+
| ID | ParentID | FullPath |
+----+----------+-------------+
| 1 | NULL | (1) |
| 2 | 1 | (1)/(2) |
| 3 | 2 | (1)/(2)/(3) |
| 4 | NULL | (4) |
| 5 | 4 | (4)/(5) |
| 6 | 4 | (4)/(6) |
| 7 | 6 | (4)/(6)/(7) |
+----+----------+-------------+
如何检索项目的祖先? 例如,如果我正在寻找 ID 3 的祖先,我会得到 1 和 2。 同样,如果我正在寻找 7,我会得到 4 和 6(注 5 不存在)。
我知道我可以通过解析列并使用动态 SQL 来避免将 CTE 与 FullPath 一起使用,但我很难创建查询。
编辑: 我想要一个查询,让我获得项目的每个祖先行。例如,如果我想要 7 的祖先,查询将 return 以下 table:
+----+----------+-------------+
| ID | ParentID | FullPath |
+----+----------+-------------+
| 4 | NULL | (4) |
| 6 | 4 | (4)/(6) |
+----+----------+-------------+
原因是因为我有更多描述该项目的列,我需要获取它们并进行比较。
如果您已经将完整路径作为数据的一部分,为什么不从中解析出祖先呢? string_split()
.
示例数据
declare @Data table
(
ID int,
ParentID int,
FullPath nvarchar(50)
);
insert into @Data (ID, ParentID, FullPath) values
(1, NULL, '(1)' ),
(2, 1 , '(1)/(2)' ),
(3, 2 , '(1)/(2)/(3)' ),
(4, NULL, '(4)' ),
(5, 4 , '(4)/(5)' ),
(6, 4 , '(4)/(6)' ),
(7, 6 , '(4)/(6)/(7)' );
解决方案
select d.ID as SelectedID,
da.*
from @Data d
cross apply string_split(d.FullPath, '/') s
join @Data da -- data ancestor
on da.ID = convert(int, replace(replace(s.value, '(', ''), ')', ''))
where d.ID = 7
and d.ID <> da.ID -- filter out ID itself
order by d.ID, da.ID;
结果
SelectedID ID ParentID FullPath
----------- ----------- ----------- -------------
7 4 NULL (4)
7 6 4 (4)/(6)
这可以使用 self-join 来完成:-
DECLARE @Hierarchy TABLE
(
ID int PRIMARY KEY
, ParentID int
-- Non-clustered index key can be up to 1600 bytes on SQL 2016+
, FullPath varchar(1028) UNIQUE
)
;
INSERT @Hierarchy
( ID, ParentID, FullPath )
VALUES
( 1, NULL, '(1)' )
, ( 2, 1 , '(1)/(2)' )
, ( 3, 2 , '(1)/(2)/(3)' )
, ( 4, NULL, '(4)' )
, ( 5, 4 , '(4)/(5)' )
, ( 6, 4 , '(4)/(6)' )
, ( 7, 6 , '(4)/(6)/(7)' )
;
SELECT N.ID
, A.ID
, A.ParentID
, A.FullPath
FROM @Hierarchy AS N
INNER JOIN @Hierarchy AS A ON N.FullPath LIKE A.FullPath + '%'
WHERE N.ID <> A.ID
ORDER BY N.ID ASC
, A.ID ASC
;
在我的测试中,上述查询确实使用了 FullPath 上的唯一索引。这是一种替代方法,但它会进行全面扫描而不是使用索引:-
SELECT N.ID
, A.ID
, A.ParentID
, A.FullPath
FROM @Hierarchy AS N
INNER JOIN @Hierarchy AS A ON SUBSTRING(N.FullPath, 1, LEN(A.FullPath)) = A.FullPath
WHERE N.ID <> A.ID
ORDER BY N.ID ASC
, A.ID ASC
;