SQL Server: How to limit CTE recursion to rows just recursivly added?

前端 未结 8 1056
北海茫月
北海茫月 2020-12-08 23:12

Simpler Example

Let\'s try a simpler example, so people can wrap their heads around the concepts, and have a practical example that you can copy&paste into SQL

相关标签:
8条回答
  • 2020-12-08 23:31

    Well, your answer is not quite that obvious :-)

    WITH (NodeChildren) AS
    {
       --initialization
       SELECT ParentNodeID, ChildNodeID, 1 AS GenerationsRemoved
       FROM Nodes
    

    This part is called the "anchor" part of the recursive CTE - but it should really only select one or a select few rows from your table - this selects everything!

    I guess what you're missing here is simply a suitable WHERE clause:

    WITH (NodeChildren) AS
    {
       --initialization
       SELECT ParentNodeID, ChildNodeID, 1 AS GenerationsRemoved
       FROM Nodes
       **WHERE ParentNodeID IS NULL**
    

    However, I am afraid your requirement to have not just the "straight" hierarchy, but also the grandparent-child rows, might not be that easy to satisfy.... normally recursive CTE will only ever show one level and its direct subordinates (and that down the hierarchy, of course) - it doesn't usually skip one, two or even more levels.

    Hope this helps a bit.

    Marc

    0 讨论(0)
  • 2020-12-08 23:31

    Have you tried constructing a path in the CTE and using it to identify ancestors?

    You can then subtract the descendant node depth from the ancestor node depth to calculate the GenerationsRemoved column, like so...

    DECLARE @Nodes TABLE
    (
        NodeId varchar(50) PRIMARY KEY NOT NULL,
        ParentNodeId varchar(50) NULL
    )
    
    INSERT INTO @Nodes (NodeId, ParentNodeId) VALUES ('A', NULL)
    INSERT INTO @Nodes (NodeId, ParentNodeId) VALUES ('B', 'A')
    INSERT INTO @Nodes (NodeId, ParentNodeId) VALUES ('C', 'B')
    
    DECLARE @Hierarchy TABLE
    (
        NodeId varchar(50) PRIMARY KEY NOT NULL,
        ParentNodeId varchar(50) NULL,
        Depth int NOT NULL,
        [Path] varchar(2000) NOT NULL
    )
    
    WITH Hierarchy AS
    (
        --initialization
        SELECT NodeId, ParentNodeId, 0 AS Depth, CONVERT(varchar(2000), NodeId) AS [Path]
        FROM @Nodes
        WHERE ParentNodeId IS NULL
    
        UNION ALL
    
        --recursive execution
        SELECT n.NodeId, n.ParentNodeId, p.Depth + 1, CONVERT(varchar(2000), p.[Path] + '/' + n.NodeId)
        FROM Hierarchy AS p
        INNER JOIN @Nodes AS n
        ON p.NodeId = n.ParentNodeId
    )
    INSERT INTO @Hierarchy
    SELECT *
    FROM Hierarchy
    
    SELECT parent.NodeId AS AncestorNodeId, child.NodeId AS DescendantNodeId, child.Depth - parent.Depth AS GenerationsRemoved
    FROM @Hierarchy AS parent
    INNER JOIN @Hierarchy AS child
    ON child.[Path] LIKE parent.[Path] + '/%'
    
    0 讨论(0)
  • 2020-12-08 23:38

    This breaks the recursion limit imposed on Chris Shaffer's answer.

    I create a table with a cycle:

    CREATE TABLE ##Nodes
    (
       NodeID varchar(50) PRIMARY KEY NOT NULL,
       ParentNodeID varchar(50) NULL
    )
    
    INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('A', 'C');
    INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('B', 'A');
    INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('C', 'B');
    

    In cases where there is a potential cycle (i.e. ParentNodeId IS NOT NULL), the generation removed is started at 2. We can then identity cycles by checking (P.ParentNodeID == N.NodeID), which we simply don't add it. Afterwards, we append the omitted generation remove = 1.

    WITH ParentNodes AS
    (
       --initialization
       SELECT ParentNodeID, NodeID, 1 AS GenerationsRemoved
       FROM ##Nodes
       WHERE ParentNodeID IS NULL
    
       UNION ALL
    
       SELECT P.ParentNodeID, N.NodeID, 2 AS GenerationsRemoved
       FROM ##Nodes N
       JOIN ##Nodes P ON N.ParentNodeID=P.NodeID
       WHERE P.ParentNodeID IS NOT NULL
    
       UNION ALL
    
       ----recursive execution
       SELECT P.ParentNodeID, N.NodeID, P.GenerationsRemoved + 1
       FROM ParentNodes AS P
         INNER JOIN ##Nodes AS N
         ON P.NodeID = N.ParentNodeID
       WHERE P.ParentNodeID IS NULL OR P.ParentNodeID <> N.NodeID
    
    ),
    Nodes AS (
       SELECT ParentNodeID, NodeID, 1 AS GenerationsRemoved 
       FROM ##Nodes 
       WHERE ParentNodeID IS NOT NULL
    
       UNION ALL
    
       SELECT ParentNodeID, NodeID, GenerationsRemoved FROM ParentNodes
    )
    SELECT ParentNodeID, NodeID, GenerationsRemoved
    FROM Nodes
    ORDER BY ParentNodeID, NodeID, GenerationsRemoved
    
    0 讨论(0)
  • 2020-12-08 23:40
    with cte as
    (
        select a=65, L=1
        union all
        select a+1, L=L+1
        from cte
        where L<=100
    )
    select 
    IsRecursion=Case When L>1 then 'Recursion' else 'Not Recursion' end,
    AsciiValue=a,
    AsciiCharacter=char(a)
    from cte
    
    1. Create a column containing the current level.
    2. Check if the level is >1

    My example here shows a recursive CTE that stops recursion after 100 levels (the max). As a bonus, it displays a bunch of ASCII characters and the corresponding numeric value.

    0 讨论(0)
  • 2020-12-08 23:43

    If I understand your intentions you can get you result by doing something like this:

    DECLARE @StartID INT;
    SET @StartID = 1;
    WITH CTE (ChildNodeID, ParentNodeID, [Level]) AS
    (
      SELECT  t1.ChildNodeID, 
              t1.ParentNodeID, 
              0
      FROM tblNodes AS t1
      WHERE ChildNodeID = @StartID
      UNION ALL
      SELECT  t1.ChildNodeID, 
              t1.ParentNodeID, 
              t2.[Level]+1
      FROM tblNodes AS t1
        INNER JOIN CTE AS t2 ON t1.ParentNodeID = t2.ChildNodeID    
    )
    SELECT t1.ChildNodeID, t2.ChildNodeID, t1.[Level]- t2.[Level] AS GenerationsDiff
    FROM CTE AS t1
      CROSS APPLY CTE t2
    

    This will return the generation difference between all nodes, you can modify it for you exact needs.

    0 讨论(0)
  • 2020-12-08 23:44

    Aside: do you have SQL Server 2008? This might be suited to the hierarchyid data type.

    0 讨论(0)
提交回复
热议问题