在T-SQL中访问同一级别的父节点

在T-SQL中访问同一级别的父节点,sql,tsql,Sql,Tsql,假设我有这样一个数据库模式: RowId ParentId Name ------ ---------- ------ 1 NULL Level1 2 NULL Level2 3 1 Leaf1 4 1 Leaf2 5 2 Leaf1 6 3 LeafX 基本上,这棵树看起来是这样的: Level1 Leaf1

假设我有这样一个数据库模式:

RowId   ParentId   Name
------ ---------- ------
1       NULL      Level1
2       NULL      Level2
3       1         Leaf1
4       1         Leaf2
5       2         Leaf1
6       3         LeafX
基本上,这棵树看起来是这样的:

Level1
       Leaf1
             LeafX
       Leaf2
Level2
       Leaf1
我需要以最高效和动态的方式提取LeafX的所有祖先级别

因此它将输出:Level2的Leaf1、Leaf2和Leaf1


如何在T-SQL中实现这一点?谢谢

有几种方法可以做到这一点。我最喜欢的是创建特殊的表树,在其中存储每个节点的每个父节点。 所以如果你有这样的结构

RowId   ParentId   Name
------ ---------- ------
1       NULL      Level1
2       NULL      Level2
3       1         Leaf1
4       1         Leaf2
5       2         Leaf1
6       3         LeafX
你的父母桌上的树看起来像

RowId   ParentId
------ ----------
1       1     
2       2     
3       3
3       1        
4       4
4       1        
5       5
5       2        
6       6
6       1
6       3        
然后,当您需要检索所有子对象时,只需编写

select RowID from Trees_Parents where ParentId = 1
我将行self存储在此表中以避免联合,如果您不需要它,可以编写

select RowID from Trees_Parents where ParentId = 1 and ParentId <> RowId
为了所有的父母,你要写信

select ParentId from Trees_Parents where RowId = 6 and ParentId <> RowId
您还可以将表名存储在表树中,以便对不同的表使用它


另一种方法是编写recursive WITH子句,但如果您的树很大并且不经常更改,我认为最好将父数据存储在附加表中,有几种方法可以做到这一点。我最喜欢的是创建特殊的表树,在其中存储每个节点的每个父节点。 所以如果你有这样的结构

RowId   ParentId   Name
------ ---------- ------
1       NULL      Level1
2       NULL      Level2
3       1         Leaf1
4       1         Leaf2
5       2         Leaf1
6       3         LeafX
你的父母桌上的树看起来像

RowId   ParentId
------ ----------
1       1     
2       2     
3       3
3       1        
4       4
4       1        
5       5
5       2        
6       6
6       1
6       3        
然后,当您需要检索所有子对象时,只需编写

select RowID from Trees_Parents where ParentId = 1
我将行self存储在此表中以避免联合,如果您不需要它,可以编写

select RowID from Trees_Parents where ParentId = 1 and ParentId <> RowId
为了所有的父母,你要写信

select ParentId from Trees_Parents where RowId = 6 and ParentId <> RowId
您还可以将表名存储在表树中,以便对不同的表使用它


另一种方法是编写recursive WITH子句,但如果您的树很大并且不经常更改,我认为最好将父数据存储在附加表中,您可以使用recursive解决方案。您需要获取深度=节点深度-1的所有节点

declare @Temp table (RowId int, ParentId int, Name nvarchar(128))

insert into @Temp
select 1, null, 'Level1' union all
select 2, null, 'Level2' union all
select 3, 1, 'Leaf1' union all
select 4, 1, 'Leaf2' union all
select 5, 2, 'Leaf3' union all
select 6, 3, 'LeafX';

with Parents
as
(
    select T.RowId, 0 as Depth from @Temp as T where T.ParentId is null
    union all
    select T.RowId, P.Depth + 1
    from Parents as P
        inner join @Temp as T on T.ParentId = P.RowId
)
select T.Name
from Parents as P
    outer apply (select TT.Depth from Parents as TT where TT.RowId = 6) as CALC
    left outer join @Temp as T on T.RowId = P.RowId
where P.Depth = CALC.Depth - 1

你们可以使用递归的解决方案。您需要获取深度=节点深度-1的所有节点

declare @Temp table (RowId int, ParentId int, Name nvarchar(128))

insert into @Temp
select 1, null, 'Level1' union all
select 2, null, 'Level2' union all
select 3, 1, 'Leaf1' union all
select 4, 1, 'Leaf2' union all
select 5, 2, 'Leaf3' union all
select 6, 3, 'LeafX';

with Parents
as
(
    select T.RowId, 0 as Depth from @Temp as T where T.ParentId is null
    union all
    select T.RowId, P.Depth + 1
    from Parents as P
        inner join @Temp as T on T.ParentId = P.RowId
)
select T.Name
from Parents as P
    outer apply (select TT.Depth from Parents as TT where TT.RowId = 6) as CALC
    left outer join @Temp as T on T.RowId = P.RowId
where P.Depth = CALC.Depth - 1

这会给你想要的结果

;with C as
(
  select T.rowid,
         T.parentid,
         T.name,
         1 as Lvl
  from YourTable as T
  where T.parentid is null
  union all
  select T.rowid,
         T.parentid,
         T.name,
         C.Lvl + 1
  from YourTable as T
    inner join C
       on T.parentid = C.rowid
)
select *
from C
where C.Lvl = (
               select C.lvl-1
               from C
               where C.name = 'LeafX'
              )
更新 这对你来说可能更快。你必须测试你的数据

declare @Level int;

with C as
(
  select T.rowid,
         T.parentid
  from @t as T
  where T.name = 'LeafX'
  union all
  select T.rowid,
         T.parentid
  from @t as T
    inner join C
       on T.rowid = C.parentid
)
select @Level = count(*) - 1
from C;

with C as
(
  select T.rowid,
         T.parentid,
         T.name,
         1 as Lvl
  from @t as T
  where T.parentid is null
  union all
  select T.rowid,
         T.parentid,
         T.name,
         C.Lvl + 1
  from @t as T
    inner join C
       on T.parentid = C.rowid
  where C.Lvl < @Level
)
select *
from C
where C.Lvl = @Level;

这会给你想要的结果

;with C as
(
  select T.rowid,
         T.parentid,
         T.name,
         1 as Lvl
  from YourTable as T
  where T.parentid is null
  union all
  select T.rowid,
         T.parentid,
         T.name,
         C.Lvl + 1
  from YourTable as T
    inner join C
       on T.parentid = C.rowid
)
select *
from C
where C.Lvl = (
               select C.lvl-1
               from C
               where C.name = 'LeafX'
              )
更新 这对你来说可能更快。你必须测试你的数据

declare @Level int;

with C as
(
  select T.rowid,
         T.parentid
  from @t as T
  where T.name = 'LeafX'
  union all
  select T.rowid,
         T.parentid
  from @t as T
    inner join C
       on T.rowid = C.parentid
)
select @Level = count(*) - 1
from C;

with C as
(
  select T.rowid,
         T.parentid,
         T.name,
         1 as Lvl
  from @t as T
  where T.parentid is null
  union all
  select T.rowid,
         T.parentid,
         T.name,
         C.Lvl + 1
  from @t as T
    inner join C
       on T.parentid = C.rowid
  where C.Lvl < @Level
)
select *
from C
where C.Lvl = @Level;

我不太明白这一点,为什么RowId会重复标识符?好吧,你把RowId和它的父对象放到表中,然后把RowId和父对象的父对象放到表中,依此类推。我使用主表上的after update/insert/delete触发器完成了这项操作。基本思想是-将数据插入主表后,扫描其他表并获取所有ParentId,其中RowId=new recordParentId+将RowId插入为ParentId例如,插入RowId=1,将RowId=1 ParentId=1放入其他表中。ParentId为null,所以您不将其放入其他表中。当您放置rowId=3时,您将插入rowId=3,ParentId=3,然后在rowId=1的位置获取所有ParentId。当您需要更新和删除记录时,这会稍微复杂一些,但在这之后,如果您在附加表中有rowId和ParentId的索引,那么它的工作速度会非常快。因为数据已经存在,所以我无法真正处理此问题。您是否建议我每次访问这些级别时都创建一个表变量?您可以创建表、创建触发器,然后用重新计算的数据填充其他表。我还为您的答案添加了更慢的递归解决方案,可能会有所帮助。我不太明白这一点,为什么RowId会重复标识符?好吧,您将RowId及其父项放入表中,然后将RowId及其父项放入表中,依此类推。我使用主表上的after update/insert/delete触发器完成了这项操作。基本思想是-将数据插入主表后,扫描其他表并获取所有ParentId,其中RowId=new recordParentId+将RowId插入为ParentId例如,插入RowId=1,将RowId=1 ParentId=1放入其他表中。ParentId为null,所以您不将其放入其他表中。当您放置rowId=3时,您将插入rowId=3,ParentId=3,然后在rowId=1的位置获取所有ParentId。当您需要更新和删除记录时,这会稍微复杂一些,但在这之后,如果您在附加表中有rowId和ParentId的索引,那么它的工作速度会非常快。因为数据已经存在,所以我无法真正处理此问题。您是否建议我每次访问这些级别时都创建一个表变量?您可以创建表、创建触发器,然后用重新计算的数据填充其他表。我还为您的答案添加了更慢的递归解决方案,可能这将有助于父节点从不在同一级别上-根据定义,它们总是比任何给定节点高一级别…父节点从不在同一级别上-根据定义,它们总是比任何给定节点高一级别…有人否决了我的答案。我的回答发现了问题中描述的leafx祖先的兄弟姐妹。我很想否决其他两个答案,因为他们没有。我添加了测试数据和结果来证明我的答案是正确的。我查询了你的答案,它应该返回3-Leaf1、4-Leaf2和5-Leaf1