【问题标题】:SQL Sever parent, child, child relationships within tableSQL Server 表内的父、子、子关系
【发布时间】:2017-01-24 23:02:34
【问题描述】:

我有以下项目表,其中包含一个名称列表,并映射回表中的父字段 -

id  nameVal parentId  
1   A   NULL  
2   B   NULL  
3   C   NULL  
4   D   NULL  
5   E   NULL  
6   A1  1  
7   A2  6  
8   A3  1  
9   A4  7  
10  B1  2  
11  B2  2  

距离父记录可能不止一步 - A、A1、A4 都是相关的等等,如下所示...

A1 => A
A2 => A1 => A
A3 => A
A4 => A2 => A1 => A

所以我要做的是提取存在关系的所有记录,即 A4 会带回所有 A,因为有指向原始 A 记录的链接。

这可能吗?

【问题讨论】:

  • 查看公用表表达式 (CTE)。如果您希望在同一个查询中同时包含父子关系(但可行),那么这并不简单。见stackoverflow.com/q/4740748/67392

标签: sql sql-server tsql


【解决方案1】:

您可以使用递归 CTE 来做到这一点。关键是得到父母。这是一种方法:

with cte as (
      select id, nameval, id as orig
      from t
      where parentid is null
      union all
      select t.nameval, cte.orig
      from cte join
           t
           on t.parentid = cte.id
    )
select cte.*
from cte
where cte.orig = (select cte2.orig from cte cte2 where ct2.nameval = 'A4');

【讨论】:

    【解决方案2】:

    也许有点多余,但请考虑以下几点:

    您可以设置顶部节点(null将默认为整个层次结构)

    您还可以设置过滤器。对于没有过滤器、单个 ID 或分隔的 ID 字符串,这可以为空。

    Declare @T table (id int,nameVal varchar(50),parentId int)
    Insert into @T values 
    (1   ,'A',   NULL),  
    (2   ,'B',   NULL),  
    (3   ,'C',   NULL),
    (4   ,'D',   NULL),  
    (5   ,'E',   NULL),  
    (6   ,'A1',  1),  
    (7   ,'A2',  6),  
    (8   ,'A3',  1),  
    (9   ,'A4',  7),  
    (10  ,'B1',  2),  
    (11  ,'B2',  2)  
    
    Declare @Top    int         = null      --<<  Sets top of Hier Try 6
    Declare @Nest   varchar(25) = '|-----'  --<<  Optional: Added for readability
    Declare @Filter varchar(25) = '7'       --<<  Empty for All or try '7,10'
    
    ;with cteP as (
          Select Seq  = cast(10000+Row_Number() over (Order by nameVal) as varchar(500))
                ,ID
                ,parentId
                ,Lvl=1
                ,nameVal 
          From   @T 
          Where  IsNull(@Top,-1) = case when @Top is null then isnull(parentId,-1) else ID end
          Union  All
          Select Seq  = cast(concat(p.Seq,'.',10000+Row_Number() over (Order by r.nameVal)) as varchar(500))
                ,r.ID
                ,r.parentId
                ,p.Lvl+1
                ,r.nameVal
          From   @T r
          Join   cteP p on r.parentId = p.ID)
         ,cteR1 as (Select *,R1=Row_Number() over (Order By Seq) From cteP)
         ,cteR2 as (Select A.Seq,A.ID,R2=Max(B.R1) From cteR1 A Join cteR1 B on (B.Seq like A.Seq+'%') Group By A.Seq,A.ID )
         ,cte   as (
                    Select A.R1  
                          ,B.R2
                          ,A.ID
                          ,A.parentId
                          ,A.Lvl
                          ,nameVal = Replicate(@Nest,A.Lvl-1) + A.nameVal
                     From cteR1 A
                     Join cteR2 B on A.ID=B.ID
                   )
    Select Distinct A.*
     From  cte A
     Join (
            Select A.R1,A.R2 
             From  cte A
             Join (Select R1 from cte Where IIF(@Filter='',1,0)+CharIndex(concat(',',ID,','),concat(',',@Filter+','))>0) B
               on B.R1 between A.R1 and A.R2
          ) B on A.R1 between B.R1 and B.R2
     Order By A.R1
    

    退货

    现在,如果你设置@Filter = '7,10',你会得到

    如果你设置@Filter = '',你会得到

    【讨论】:

      【解决方案3】:

      您可以使用递归查询来获取所有相关行。我不知道数据是否会采用您想要的形式,因为这个问题似乎有点不清楚。但是例如

      获取一条记录和所有后代:

      with r as (
           select *
             from my_table t
            where id = 7
           union all
           select t1.*
             from            my_table t1
                  inner join r
                          on t1.parent_id = r.id
      )
      select * from r;
      

      获取记录和所有祖先:

      with r as (
           select *
             from my_table t
            where id = 7
           union all
           select t1.*
             from            my_table t1
                  inner join r
                          on t1.id = r.parent_id
      )
      select * from r;
      

      现在也许你想要孩子和祖先。这可能会有点棘手。递归在直线上效果最好,因此不会有无限循环。一种方法是将上述两个查询合并在一起。如果您的实际查询具有复杂的逻辑,您不想编写两次,那么您可以使用它来获取 ID 列表,然后在 select ... where id in (my_list) 类型的查询上运行实际查询。

      另一个考虑因素是记录是否可以有多个子项。如果我们有

      A
      A1 => A
      A10 => A1
      A11 => A1
      A2 => A
      A20 => A2
      A21 => A2
      

      你可以说这些都是相关的(通过A;有些是“表亲”)。因此,如果您从A1 搜索并合并前两个示例查询,您会得到A, A1, A10, A11...但是您是否还想要A 的其他孩子?如果是这样,您可以采取稍微不同的方法:

      首先,找到最古老的祖先:

      with r as (
           select *
             from my_table t
            where id = 7
           union all
           select t1.*
             from            my_table t1
                  inner join r
                          on t1.id = r.parent_id
      )
      select id from r where parent_id is null;
      

      然后针对该 ID 运行原始的“所有后代”查询。如果你想把它全部放在一个语句中,我认为以下 *应该 * 工作(但我不是我可以测试它的地方):

      with ancestors as (
           select *
             from my_table t
            where id = 7
           union all
           select t1.*
             from            my_table t1
                  inner join ancestors
                          on t1.id = ancestors.parent_id
      ) , related as (
           select *
             from ancestors
            where parent_id is null
           union all
           select t1.*
             from            my_table t1
                  inner join related
                          on t1.parent_id = related.id
      )
      select * from related;
      

      【讨论】:

      • 谢谢马克 - 如果我只想获取子记录,这很好用。将 id 作为 1 传递会返回所有子记录,但如果我将 id 作为 7 传递,它只会返回记录 7 和 9,因此很抱歉不清楚 - 我也想返回父记录,因此提供 id=7 它会返回记录 1,6,7,8,9
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多