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 |
+-------+-------+----+--------+--------+--------+--------+--------+
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With