sql递归:找到给定中间节点的树

时间:2016-11-29 22:28:41

标签: tsql

我需要获得给定某个节点的相关节点树,但不是必需的顶级节点。我有一个使用两个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

谢谢

2 个答案:

答案 0 :(得分:1)

这是一种技术,您可以选择整个层次结构,包含其所有子节点的特定节点,甚至是已过滤的列表以及它们如何滚动。

注意:请参阅DECLAREs旁边的评论

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

答案 1 :(得分:1)

咩。这避免了使用两个CTE,但结果是一个蛮力的kludge几乎不符合“光滑”的条件,因为如果你的桌子大小合适,它就不会有效。它会:

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

我在TargetId出现在多个层次结构中的偶然事件中输入了“TreeNumber”列,或者如果您在一次传递中有多个值要检查。添加“深度”以使输出更清晰。

像John 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