patternsqlMajor
Using EXCEPT in a recursive common table expression
Viewed 0 times
expressionrecursiveusingexceptcommontable
Problem
Why does the following query return infinite rows? I would have expected the
I came across this while trying to answer a question on Stack Overflow.
EXCEPT clause to terminate the recursion..with cte as (
select *
from (
values(1),(2),(3),(4),(5)
) v (a)
)
,r as (
select a
from cte
where a in (1,2,3)
union all
select a
from (
select a
from cte
except
select a
from r
) x
)
select a
from rI came across this while trying to answer a question on Stack Overflow.
Solution
The BOL description of recursive CTEs describes the semantics of recursive execution as being as follows:
Note the above is a logical description. The physical order of operations can be somewhat different as illustrated here
Applying this to your CTE I would expect an infinite loop with the following pattern
Because
is the Anchor expression. This clearly returns
Thereafter the recursive expression runs
With
This is not what actually happens however. These are the results of the first 5 invocations
From using
As discussed by @Quassnoi in this blog post. The pattern of observed results is as if each invocation is doing
Edit: After reading SQL Kiwi's excellent answer it is clear both why this occurs and that this is not the whole story in that there is still loads of stuff left on the stack that never can get processed.
Anchor Emits
3 popped off stack, Stack Contents
The LASJ returns
5 popped off stack, Stack Contents
The LASJ returns
4 popped off stack, Stack Contents
The LASJ returns
5 popped off stack, Stack Contents
The LASJ returns
If you try and replace the recursive member with the logically equivalent (in the absence of duplicates / NULLs) expression
This is not allowed and raises the error "Recursive references are not allowed in subqueries." so perhaps it is an oversight that
Addition:
Microsoft have now responded to my Connect Feedback as below
Jack's guess is correct: this should have been a syntax error;
recursive references should indeed not be allowed in
We plan to address this bug in an upcoming service release. In the
meantime, I would suggest to avoid recursive references in
clauses.
In restricting recursion over
which has included this restriction ever since recursion was
introduced (in 1999 I believe). There is no widespread agreement over
what the semantics should be for recursion over
"unstratified negation") in declarative languages such as SQL. In
addition, it is notoriously hard (if not impossible) to implement such
semantics efficiently (for reasonably sized databases) in an RDBMS
system.
And looks like the eventual implementation was made in 2014 for databases with compatibility level of 120 or higher.
Recursive references in an EXCEPT clause generates an error in compliance with the ANSI SQL standard.
- Split the CTE expression into anchor and recursive members.
- Run the anchor member(s) creating the first invocation or base result set (T0).
- Run the recursive member(s) with Ti as an input and Ti+1 as an output.
- Repeat step 3 until an empty set is returned.
- Return the result set. This is a UNION ALL of T0 to Tn.
Note the above is a logical description. The physical order of operations can be somewhat different as illustrated here
Applying this to your CTE I would expect an infinite loop with the following pattern
+-----------+---------+---+---+---+
| Invocation| Results |
+-----------+---------+---+---+---+
| 1 | 1 | 2 | 3 | |
| 2 | 4 | 5 | | |
| 3 | 1 | 2 | 3 | |
| 4 | 4 | 5 | | |
| 5 | 1 | 2 | 3 | |
+-----------+---------+---+---+---+Because
select a
from cte
where a in (1,2,3)is the Anchor expression. This clearly returns
1,2,3 as T0Thereafter the recursive expression runs
select a
from cte
except
select a
from rWith
1,2,3 as input that will yield an output of 4,5 as T1 then plugging that back in for the next round of recursion will return 1,2,3 and so on indefinitely.This is not what actually happens however. These are the results of the first 5 invocations
+-----------+---------+---+---+---+
| Invocation| Results |
+-----------+---------+---+---+---+
| 1 | 1 | 2 | 3 | |
| 2 | 1 | 2 | 4 | 5 |
| 3 | 1 | 2 | 3 | 4 |
| 4 | 1 | 2 | 3 | 5 |
| 5 | 1 | 2 | 3 | 4 |
+-----------+---------+---+---+---+From using
OPTION (MAXRECURSION 1) and adjusting upwards in increments of 1 it can be seen that it enters a cycle where each successive level will continually toggle between outputting 1,2,3,4 and 1,2,3,5.As discussed by @Quassnoi in this blog post. The pattern of observed results is as if each invocation is doing
(1),(2),(3),(4),(5) EXCEPT (X) where X is the last row from the previous invocation.Edit: After reading SQL Kiwi's excellent answer it is clear both why this occurs and that this is not the whole story in that there is still loads of stuff left on the stack that never can get processed.
Anchor Emits
1,2,3 to client Stack Contents 3,2,13 popped off stack, Stack Contents
2,1The LASJ returns
1,2,4,5, Stack Contents 5,4,2,1,2,15 popped off stack, Stack Contents
4,2,1,2,1The LASJ returns
1,2,3,4 Stack Contents 4,3,2,1,5,4,2,1,2,14 popped off stack, Stack Contents
3,2,1,5,4,2,1,2,1The LASJ returns
1,2,3,5 Stack Contents 5,3,2,1,3,2,1,5,4,2,1,2,15 popped off stack, Stack Contents
3,2,1,3,2,1,5,4,2,1,2,1The LASJ returns
1,2,3,4 Stack Contents4,3,2,1,3,2,1,3,2,1,5,4,2,1,2,1If you try and replace the recursive member with the logically equivalent (in the absence of duplicates / NULLs) expression
select a
from (
select a
from cte
where a not in
(select a
from r)
) xThis is not allowed and raises the error "Recursive references are not allowed in subqueries." so perhaps it is an oversight that
EXCEPT is even allowed in this case.Addition:
Microsoft have now responded to my Connect Feedback as below
Jack's guess is correct: this should have been a syntax error;
recursive references should indeed not be allowed in
EXCEPT clauses.We plan to address this bug in an upcoming service release. In the
meantime, I would suggest to avoid recursive references in
EXCEPTclauses.
In restricting recursion over
EXCEPT we follow the ANSI SQL standard,which has included this restriction ever since recursion was
introduced (in 1999 I believe). There is no widespread agreement over
what the semantics should be for recursion over
EXCEPT (also called"unstratified negation") in declarative languages such as SQL. In
addition, it is notoriously hard (if not impossible) to implement such
semantics efficiently (for reasonably sized databases) in an RDBMS
system.
And looks like the eventual implementation was made in 2014 for databases with compatibility level of 120 or higher.
Recursive references in an EXCEPT clause generates an error in compliance with the ANSI SQL standard.
Code Snippets
+-----------+---------+---+---+---+
| Invocation| Results |
+-----------+---------+---+---+---+
| 1 | 1 | 2 | 3 | |
| 2 | 4 | 5 | | |
| 3 | 1 | 2 | 3 | |
| 4 | 4 | 5 | | |
| 5 | 1 | 2 | 3 | |
+-----------+---------+---+---+---+select a
from cte
where a in (1,2,3)select a
from cte
except
select a
from r+-----------+---------+---+---+---+
| Invocation| Results |
+-----------+---------+---+---+---+
| 1 | 1 | 2 | 3 | |
| 2 | 1 | 2 | 4 | 5 |
| 3 | 1 | 2 | 3 | 4 |
| 4 | 1 | 2 | 3 | 5 |
| 5 | 1 | 2 | 3 | 4 |
+-----------+---------+---+---+---+select a
from (
select a
from cte
where a not in
(select a
from r)
) xContext
StackExchange Database Administrators Q#9638, answer score: 32
Revisions (0)
No revisions yet.