【问题标题】:sql recursion: find tree given middle nodesql递归:找到给定中间节点的树
【发布时间】:2016-11-30 07:06:12
【问题描述】:

我需要得到一个给定节点的相关节点树,但不是必要的顶部节点。我有一个使用两个 CTE 的解决方案,因为我正在努力将它全部压缩到一个 CTE 中:)。可能有人有一个巧妙的解决方案来避免使用两个 CTE?这是我正在玩的一些代码:

DECLARE @temp AS TABLE (ID INT, ParentID INT)
INSERT INTO @temp
SELECT 1 ID, NULL AS ParentID
UNION ALL
SELECT 2, 1
UNION ALL
SELECT 3, 2
UNION ALL
SELECT 4, 3
UNION ALL
SELECT 5, 4
UNION ALL
SELECT 6, NULL
UNION ALL
SELECT 7, 6
UNION ALL
SELECT 8, 7

DECLARE @startNode INT = 4
;WITH TheTree (ID,ParentID)
AS (
    SELECT ID, ParentID
    FROM @temp
    WHERE ID = @startNode
    UNION ALL 
    SELECT t.id, t.ParentID
    FROM @temp t
    JOIN TheTree tr ON t.ParentID = tr.ID
    )
SELECT * FROM TheTree

;WITH Up(ID,ParentID)
AS (
    SELECT t.id, t.ParentID
    FROM @temp t
    WHERE t.ID = @startNode
    UNION ALL 
    SELECT t.id, t.ParentID
    FROM @temp t
    JOIN Up c ON t.id = c.ParentID
    )
    --SELECT * FROM Up
,TheTree (ID,ParentID)
AS (
    SELECT ID, ParentID
    FROM Up
    WHERE ParentID is null
    UNION ALL 
    SELECT t.id, t.ParentID
    FROM @temp t
    JOIN TheTree tr ON t.ParentID = tr.ID
    )
SELECT * FROM TheTree

谢谢

【问题讨论】:

    标签: tsql


    【解决方案1】:

    嗯。这避免了使用两个 CTE,但结果是一个蛮力拼凑,几乎不能称得上是“时尚”,因为如果你的桌子非常大,它就不会有效。它将:

    • 递归构建所有可能的层次结构
    • 在构建它们时,在找到目标 NodeId 时标记它
    • 只返回目标树

    如果 TargetId 出现在多个层次结构中的可能性不大,或者如果您曾经有多个值要在一次检查中检查,我会在列“TreeNumber”中添加。添加了“深度”以使输出更清晰。

    像@John 那样的更复杂的解决方案可能会做,并且可以通过更详细的表格结构来完成更多更微妙的技巧。

    DECLARE @startNode INT = 4
    
    ;WITH cteAllTrees (TreeNumber, Depth, ID, ParentID, ContainsTarget)
    AS (
        SELECT
           row_number() over (order by ID)  TreeNumber
          ,1
          ,ID
          ,ParentID
          ,case
             when ID = @startNode then 1
             else 0
           end  ContainsTarget
         FROM @temp
         WHERE ParentId is null
        UNION ALL 
        SELECT
           tr.TreeNumber
          ,tr.Depth + 1
          ,t.id
          ,t.ParentID
          ,case
             when tr.ContainsTarget = 1 then 1
             when t.ID = @startNode then 1
             else 0
           end  ContainsTarget
        FROM @temp t
        INNER JOIN cteAllTrees tr
         ON t.ParentID = tr.ID
        )
    SELECT
       TreeNumber
      ,Depth
      ,ID
      ,ParentId
     from cteAllTrees
     where TreeNumber in (select TreeNumber from cteAllTrees where ContainsTarget = 1)
     order by
       TreeNumber
      ,Depth
      ,ID
    

    【讨论】:

    • 非常感谢。正如您所说的'几乎不能称为“时尚”':),从性能角度来看,我认为最好使用 2 个 CTE。我记得教授谈到了带有 2 个锚点的递归(回过头来......),只是希望有人知道它并且它适用于我的情况。
    • 可能有某种方法可以在递归连接中插入 OR 子句,同时上下移动,但我不知道如何排除先前选择的行。
    • 也尝试使用此处提供的算法stackoverflow.com/questions/24611497/…,但在我的情况下它既不“时尚”也不高效。
    【解决方案2】:

    这是一种技术,您可以在其中选择整个层次结构、特定节点及其所有子节点,甚至是过滤列表以及它们的滚动方式。

    注意:请参阅 DECLARE 旁边的 cmets

    Declare @YourTable table (id int,pt int,name varchar(50))
    Insert into @YourTable values 
    (1,null,'1'),(2,1,'2'),(3,1,'3'),(4,2,'4'),(5,2,'5'),(6,3,'6'),(7,null,'7'),(8,7,'8')
    
    Declare @Top    int         = null      --<<  Sets top of Hier Try 2
    Declare @Nest   varchar(25) = '|-----'  --<<  Optional: Added for readability
    Declare @Filter varchar(25) = ''        --<<  Empty for All or try 4,6
    
    ;with cteP as (
          Select Seq  = cast(1000+Row_Number() over (Order by name) as varchar(500))
                ,ID
                ,pt
                ,Lvl=1
                ,name 
          From   @YourTable 
          Where  IsNull(@Top,-1) = case when @Top is null then isnull(pt,-1) else ID end
          Union  All
          Select Seq  = cast(concat(p.Seq,'.',1000+Row_Number() over (Order by r.name)) as varchar(500))
                ,r.ID
                ,r.pt
                ,p.Lvl+1
                ,r.name 
          From   @YourTable r
          Join   cteP p on r.pt = 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 )
    Select Distinct 
           A.R1  
          ,B.R2
          ,A.ID
          ,A.pt
          ,A.Lvl
          ,name = Replicate(@Nest,A.Lvl-1) + A.name
     From cteR1 A
     Join cteR2 B on A.ID=B.ID
     Join (Select R1 From cteR1 where IIF(@Filter='',1,0)+CharIndex(concat(',',ID,','),concat(',',@Filter+','))>0) F on F.R1 between A.R1 and B.R2
     Order By A.R1
    

    【讨论】:

    • 谢谢,但不完全是“它”。我不需要构建一棵完整的树。我在一张表中有大量单独的树(大约 5 百万条记录),其中大部分只是根树(1 个节点),大约 10% 的树有 1 到 20 个节点。我所需要的只是找到属于给定节点的树的节点列表,该节点可能是根节点,但也可以是尾节点或中间节点。但无论如何,谢谢。以上可能是有用的算法。
    猜你喜欢
    • 2012-02-10
    • 2021-03-15
    • 1970-01-01
    • 2018-06-22
    • 1970-01-01
    • 2020-09-04
    • 2017-09-26
    • 2010-10-02
    • 2013-06-15
    相关资源
    最近更新 更多