1

I have a recursive WITH query in SQL Server 2017:

;WITH rec AS (
    SELECT
       col1 AS root_order
      ,col1
      ,col2
      ,col3
      ,col4
      ,col5
      ,col6
      ,col7
      ,col8
      ,col9

    FROM
        TableA


    UNION ALL

    SELECT
        rec.root_order,
        TableA.col2,
        TableA.col3,
        TableA.col4,
        TableA.col5,
        TableA.col6,
        TableA.col7,
        TableA.col8,
        TableA.col9,
        rec.the_level

    FROM
        rec
            INNER JOIN TableA on rec.Details = TableA.Orders
    )

    SELECT DISTINCT * FROM rec

This yields a: The statement terminated. The maximum recursion 100 has been exhausted before statement completion. error.

I have tried: OPTION (maxrecursion 0) to let it continue. But when I do that, the query infinitely loops, so that doesn't work.

In Oracle, I can use CONNECT BY ROOT and CONNECT BY PRIOR and NOCYCLE, but I know things like that aren't available in SQL Server. So I found this MSDN link which suggest something of the form:

with hierarchy
as
(
select
    child, 
    parent, 
    0 as cycle,
    CAST('.' as varchar(max)) + LTRIM(child) + '.' as [path]
from
    #hier
where
    parent is null

union all

select
    c.child,
    c.parent,
    case when p.[path] like  '%.' + LTRIM(c.child) + '.%' then 1 else 0 end as cycle,
    p.[path] + LTRIM(c.child) + '.' as [path]
from
    hierarchy as p
    inner join
    #hier as c
    on p.child = c.parent
    and p.cycle = 0
)
select
    child,
    parent,
    [path]
from
    hierarchy
where
    cycle = 1;
go

For finding the cycles (or avoiding them). I cannot seem to take my current query and edit it in that fashion. How can I edit my current SQL to perform the cyclic reference detection like in the MSDN article?

Some sample data as requested here in SQL FIDDLE.

artemis
  • 6,857
  • 11
  • 46
  • 99
  • 1
    SQL Server does not implement cycle detection, unlike Oracle or DB2. PostgreSQL has a more prosaic way of doing it by implementing `UNION` in addition to `UNION ALL`. You probably will need to write a procedure. – The Impaler Sep 24 '19 at 16:51
  • No need to write a procedure. Just keep a delimited list of the full path encountered to date and check that you aren't about to join onto an item already contained in the path. i.e. the approach you have already found – Martin Smith Sep 24 '19 at 16:53
  • I think the MSDN that I posted (and others like this https://dba.stackexchange.com/questions/162298/recursive-cte-avoiding-loops) show that it is possible. What I am struggling with, probably because I can't write SQL well, is how to change their example with my codes, and that is why I am posting, as an example of doing this in SQL Server on Stack Overflow. – artemis Sep 24 '19 at 16:54
  • Can you show me some sample data? It does not need to have all columns. I just want to be sure I am understanding what you need. – gotqn Sep 24 '19 at 17:09
  • I can try. I will need to spoof it. The problem is I don't know where in the data is the cycle, which is whyI need this query. I just have massive table with a lot of column. – artemis Sep 24 '19 at 17:10
  • I posted some @gotqn – artemis Sep 24 '19 at 17:28

1 Answers1

-1

What I normally do is pretty simple. In the anchor query (the first part of the CTE), I include a value "1 AS Level" in the select list. Then in the bottom query, I select Level + 1 as the Level, so I know what depth I'm up to. Then I can just put a sanity clause into the bottom query to limit the depth i.e. WHERE LEVEL <= 10 or whatever depth you want. But yes, you still need MAXRECURSION set to 0 if you want to go above 100 levels.

Here's an example based on AdventureWorks:

WITH Materials (BillOfMaterialsID, ProductName, ProductAssemblyID, ComponentID, [Level])
AS
(
    SELECT bom.BillOfMaterialsID, 
           p.[Name], 
           bom.ProductAssemblyID, 
           bom.ComponentID, 
           1
    FROM Production.BillOfMaterials AS bom
    INNER JOIN Production.Product AS p
    ON bom.ComponentID = p.ProductID 
    AND bom.EndDate IS NULL
    WHERE bom.ProductAssemblyID IS NULL

    UNION ALL

    SELECT bom.BillOfMaterialsID, 
           p.[Name], 
           bom.ProductAssemblyID, 
           bom.ComponentID, 
           m.[Level] + 1
    FROM Production.BillOfMaterials AS bom
    INNER JOIN Production.Product AS p
    ON bom.ComponentID = p.ProductID 
    INNER JOIN Materials AS m
    ON bom.ProductAssemblyID = BOM.ComponentID 
    WHERE m.[Level] <= 5
)
SELECT m.BillOfMaterialsID, 
       m.ProductName, 
       m.ProductAssemblyID, 
       m.ComponentID, 
       m.[Level]
FROM Materials AS m
ORDER BY m.[Level], m.BillOfMaterialsID;
Greg Low
  • 1,526
  • 1
  • 4
  • 4
  • This doesn't answer the question of how to detect cycles. It could be the case the cycle occurs on the first level. – artemis Sep 25 '19 at 12:43
  • No, you can't have a cycle there as you shouldn't use the CTE itself in the first (anchor) clause. How could it then recurse (cycle)? – Greg Low Sep 26 '19 at 12:34
  • Your post doesn't answer how to detect cycles, nor does it even use any of the code in the question body. – artemis Sep 26 '19 at 14:28