Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Infinite loop in CTE when parsing self-referencing table

I'm using the following Common Table Expression to parse self-referencing table. But the CTE does not work, produces and infinite loop and generates an error:

Msg 530, Level 16, State 1, Line 1 The statement terminated. The maximum recursion 100 has been exhausted before statement completion.

How to modify this CTE to get it work?

SET NOCOUNT ON;
USE tempdb;

IF OBJECT_ID('dbo.Employees', 'U') IS NOT NULL DROP TABLE dbo.Employees;

CREATE TABLE dbo.Employees
(
  empid   INT         NOT NULL PRIMARY KEY,
  mgrid   INT         NULL     REFERENCES dbo.Employees,
  empname VARCHAR(25) NOT NULL,
  salary  MONEY       NOT NULL,
  CHECK (empid > 0)
);

INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES
  (1,  1, 'David'  , $10000.00),
  (2,  1,    'Eitan'  ,  $7000.00),
  (3,  1,    'Ina'    ,  $7500.00),
  (4,  2,    'Seraph' ,  $5000.00),
  (5,  2,    'Jiru'   ,  $5500.00),
  (6,  2,    'Steve'  ,  $4500.00),
  (7,  3,    'Aaron'  ,  $5000.00),
  (8,  5,    'Lilach' ,  $3500.00),
  (9,  7,    'Rita'   ,  $3000.00),
  (10, 5,    'Sean'   ,  $3000.00),
  (11, 7,    'Gabriel',  $3000.00),
  (12, 9,    'Emilia' ,  $2000.00),
  (13, 9,    'Michael',  $2000.00),
  (14, 9,    'Didi'   ,  $1500.00);

; with  Tree as
        (
        SELECT  empid
        ,       mgrid
        ,       1 as lv
        ,       1 as level1
        ,       null as level2
        ,       null as level3
        ,       null as level4
        ,       null as level5
        FROM    Employees
        WHERE   empid = 1 and mgrid = 1
        UNION ALL
        SELECT  E.empid
        ,       E.mgrid
        ,       T.lv + 1
        ,       T.level1
        ,       case when T.lv = 1 then E.empid else t.level2 end
        ,       case when T.lv = 2 then E.empid else t.level3 end
        ,       case when T.lv = 3 then E.empid else t.level4 end
        ,       case when T.lv = 4 then E.empid else t.level5 end
        FROM    Employees AS E
        JOIN    Tree T
        ON      E.mgrid = T.empid
        )
select  *
from Tree
order by empid

Preferred output is

+-------+-------+----+--------+--------+--------+--------+--------+
| empid | mgrid | lv | level1 | level2 | level3 | level4 | level5 |
+-------+-------+----+--------+--------+--------+--------+--------+
|     1 |     1 |  1 |      1 | NULL   | NULL   | NULL   | NULL   |
|     2 |     1 |  2 |      1 | 2      | NULL   | NULL   | NULL   |
|     3 |     1 |  2 |      1 | 3      | NULL   | NULL   | NULL   |
|     4 |     2 |  3 |      1 | 2      | 4      | NULL   | NULL   |
|     5 |     2 |  3 |      1 | 2      | 5      | NULL   | NULL   |
|     6 |     2 |  3 |      1 | 2      | 6      | NULL   | NULL   |
|     7 |     3 |  3 |      1 | 3      | 7      | NULL   | NULL   |
|     8 |     5 |  4 |      1 | 2      | 5      | 8      | NULL   |
|     9 |     7 |  4 |      1 | 3      | 7      | 9      | NULL   |
|    10 |     5 |  4 |      1 | 2      | 5      | 10     | NULL   |
|    11 |     7 |  4 |      1 | 3      | 7      | 11     | NULL   |
|    12 |     9 |  5 |      1 | 3      | 7      | 9      | 12     |
|    13 |     9 |  5 |      1 | 3      | 7      | 9      | 13     |
|    14 |     9 |  5 |      1 | 3      | 7      | 9      | 14     |
+-------+-------+----+--------+--------+--------+--------+--------+
like image 605
jrara Avatar asked Dec 05 '12 07:12

jrara


People also ask

Can CTE reference itself?

A recursive common table expression (CTE) is a CTE that references itself. By doing so, the CTE repeatedly executes, returns subsets of data, until it returns the complete result set.

What is option Maxrecursion 0 in SQL Server?

The MAXRECURSION value specifies the number of times that the CTE can recur before throwing an error and terminating. You can provide the MAXRECURSION hint with any value between 0 and 32,767 within your T-SQL query, with MAXRECURSION value equal to 0 means that no limit is applied to the recursion level.


1 Answers

The reason of an infinite loop is the first record where empid=mgrid. To handle this issue you should include a cumulative field (levels in this example) to store mgrid you have already processed and check if emid is already in this list to avoid a loop.

Here is a query:

with Tree as
        (
        SELECT  empid
        ,       mgrid
        ,       1 as lv
        ,       1 as level1
        ,       null as level2
        ,       null as level3
        ,       null as level4
        ,       null as level5
        ,       cast(mgrid as varchar(max)) levels  
        FROM    Employees
        WHERE   empid = 1 and mgrid = 1
        UNION ALL
        SELECT  E.empid
        ,       E.mgrid
        ,       T.lv + 1
        ,       T.level1
        ,       case when T.lv = 1 then E.empid else t.level2 end
        ,       case when T.lv = 2 then E.empid else t.level3 end
        ,       case when T.lv = 3 then E.empid else t.level4 end
        ,       case when T.lv = 4 then E.empid else t.level5 end
        ,       T.levels+','+cast(E.mgrid as varchar(max)) levels   

          FROM    Employees AS E
        JOIN    Tree T
        ON      E.mgrid = T.empid 
                and (','+T.levels+',' 
                      not like 
                     '%,'+cast(E.empid as varchar(max))+',%')
        )
select  *
from Tree
order by empid

And here is SQLFiddle demo

like image 165
valex Avatar answered Nov 16 '22 02:11

valex