查找分层数据的所有祖先

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
;