Pourquoi la requête suivante renvoie-t-elle des lignes infinies? Je m'attendais à ce que la clause EXCEPT
mette fin à la récursivité ..
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 r
Je suis tombé sur cela en essayant de répondre à une question sur Stack Overflow.
Voir réponse de Martin Smith pour plus d'informations sur l'état actuel de EXCEPT
dans un CTE récursif.
Pour expliquer ce que vous voyiez et pourquoi:
J'utilise une variable de table ici, pour clarifier la distinction entre les valeurs d'ancrage et l'élément récursif (cela ne change pas la sémantique).
DECLARE @V TABLE (a INTEGER NOT NULL)
INSERT @V (a) VALUES (1),(2)
;
WITH rCTE AS
(
-- Anchor
SELECT
v.a
FROM @V AS v
UNION ALL
-- Recursive
SELECT
x.a
FROM
(
SELECT
v2.a
FROM @V AS v2
EXCEPT
SELECT
r.a
FROM rCTE AS r
) AS x
)
SELECT
r2.a
FROM rCTE AS r2
OPTION (MAXRECURSION 0)
Le plan de requête est le suivant:
L'exécution commence à la racine du plan (SELECT) et le contrôle transmet l'arborescence au spouleur d'indexation, à la concaténation, puis au scan de table de niveau supérieur.
La première ligne de l'analyse passe par l'arborescence et est (a) stockée dans la bobine de pile, et (b) retournée au client. La première ligne n'est pas définie, mais supposons que c'est la ligne avec la valeur {1}, pour les besoins de l'argument. La première ligne à apparaître est donc {1}.
Le contrôle passe de nouveau au balayage de table (l'opérateur de concaténation consomme toutes les lignes de son entrée la plus externe avant d'ouvrir la suivante). L'analyse émet la deuxième ligne (valeur {2}), et celle-ci transmet à nouveau l'arborescence pour être stockée sur la pile et sortie vers le client. Le client a maintenant reçu la séquence {1}, {2}.
Adopter une convention où le haut de la pile LIFO se trouve à gauche, la pile contient désormais {2, 1}. Lorsque le contrôle passe à nouveau à l'analyse de table, il ne signale plus de lignes, et le contrôle revient à l'opérateur de concaténation, qui ouvre sa deuxième entrée (il a besoin d'une ligne pour passer au spool de pile), et le contrôle passe à la jointure intérieure pour la première fois.
La jointure intérieure appelle le spouleur de table sur son entrée externe, qui lit la ligne supérieure de la pile {2} et la supprime de la table de travail. La pile contient désormais {1}.
Après avoir reçu une ligne sur son entrée externe, la jointure interne passe le contrôle de son entrée interne à la jointure semi-gauche (LASJ). Cela demande une ligne de son entrée externe, en passant le contrôle au tri. Le tri est un itérateur de blocage, il lit donc toutes les lignes de la variable de table et les trie dans l'ordre croissant (comme cela se produit).
La première ligne émise par le tri est donc la valeur {1}. Le côté intérieur du LASJ renvoie la valeur actuelle du membre récursif (la valeur vient de sortir de la pile), qui est {2}. Les valeurs au LASJ sont {1} et {2} donc {1} est émis, car les valeurs ne correspondent pas.
Cette ligne {1} remonte l'arborescence du plan de requête vers la bobine d'indexation (pile) où elle est ajoutée à la pile, qui contient désormais {1, 1}, et émise vers le client. Le client a maintenant reçu la séquence {1}, {2}, {1}.
Le contrôle revient maintenant à la concaténation, redescend du côté intérieur (il est retourné une ligne la dernière fois, peut-être recommencer), à travers la jointure interne, au LASJ. Il lit à nouveau son entrée interne, obtenant la valeur {2} du tri.
Le membre récursif est toujours {2}, donc cette fois le LASJ trouve {2} et {2}, ce qui entraîne qu'aucune ligne n'est émise. Ne trouvant plus de lignes sur son entrée intérieure (le tri est maintenant hors des lignes), le contrôle revient à la jointure intérieure.
La jointure intérieure lit son entrée externe, ce qui fait que la valeur {1} est extraite de la pile {1, 1}, laissant la pile avec seulement {1}. Le processus se répète maintenant, avec la valeur {2} d'une nouvelle invocation de l'analyse et du tri de table passant le test LASJ et étant ajoutée à la pile, et passe au client, qui a maintenant reçu {1}, {2}, {1}, {2} ... et c'est parti.
Mon préféré explication de la bobine de pile utilisée dans les plans CTE récursifs est Craig Freedman.
La description BOL des CTE récursifs décrit la sémantique de l'exécution récursive comme suit:
Notez que ce qui précède est une description logique . L'ordre physique des opérations peut être quelque peu différent comme illustré ici
En appliquant cela à votre CTE, je m'attendrais à une boucle infinie avec le modèle suivant
+-----------+---------+---+---+---+
| Invocation| Results |
+-----------+---------+---+---+---+
| 1 | 1 | 2 | 3 | |
| 2 | 4 | 5 | | |
| 3 | 1 | 2 | 3 | |
| 4 | 4 | 5 | | |
| 5 | 1 | 2 | 3 | |
+-----------+---------+---+---+---+
Parce que
select a
from cte
where a in (1,2,3)
est l'expression d'ancrage. Cela renvoie clairement 1,2,3
Comme T0
Par la suite, l'expression récursive s'exécute
select a
from cte
except
select a
from r
Avec 1,2,3
En entrée qui produira une sortie de 4,5
En tant que T1
, Puis en le rebranchant pour la prochaine série de récursivité, vous retournerez 1,2,3
Et ainsi de suite indéfiniment.
Ce n'est cependant pas ce qui se passe réellement. Ce sont les résultats des 5 premières 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 |
+-----------+---------+---+---+---+
En utilisant OPTION (MAXRECURSION 1)
et en ajustant vers le haut par incréments de 1
, On peut voir qu'il entre dans un cycle où chaque niveau successif basculera continuellement entre la sortie de 1,2,3,4
Et 1,2,3,5
.
Comme discuté par @ Quassnoi dans ce billet de blog . Le modèle des résultats observés est comme si chaque invocation faisait (1),(2),(3),(4),(5) EXCEPT (X)
Où X
est la dernière ligne de l'invocation précédente.
Edit: Après avoir lu excellente réponse de SQL Kiwi il est clair à la fois pourquoi cela se produit et que ce n'est pas toute l'histoire dans ce il reste encore beaucoup de choses sur la pile qui ne peuvent jamais être traitées.
L'ancre émet
1,2,3
Vers le contenu de la pile client3,2,1
3 piles sautées, contenu de la pile
2,1
Le LASJ renvoie
1,2,4,5
, Contenu de la pile5,4,2,1,2,1
5 piles sautées, contenu de la pile
4,2,1,2,1
Le LASJ renvoie
1,2,3,4
Contenu de la pile4,3,2,1,5,4,2,1,2,1
4 piles sautées, contenu de la pile
3,2,1,5,4,2,1,2,1
Le LASJ renvoie
1,2,3,5
Contenu de la pile5,3,2,1,3,2,1,5,4,2,1,2,1
5 piles sautées, contenu de la pile
3,2,1,3,2,1,5,4,2,1,2,1
Le LASJ renvoie
1,2,3,4
Contenu de la pile4,3,2,1,3,2,1,3,2,1,5,4,2,1,2,1
Si vous essayez de remplacer le membre récursif par l'expression logiquement équivalente (en l'absence de doublons/NULL)
select a
from (
select a
from cte
where a not in
(select a
from r)
) x
Cela n'est pas autorisé et génère l'erreur "Les références récursives ne sont pas autorisées dans les sous-requêtes". c'est peut-être un oubli que EXCEPT
est même autorisé dans ce cas.
Ajout: Microsoft a maintenant répondu à mes Connect Feedback comme ci-dessous
Jack la supposition est correcte: cela aurait dû être une erreur de syntaxe; les références récursives ne devraient en effet pas être autorisées dans les clauses
EXCEPT
. Nous prévoyons de résoudre ce bogue dans une prochaine version de service. En attendant, je suggère d'éviter les références récursives dans les clausesEXCEPT
.En restreignant la récursivité sur
EXCEPT
nous suivons le standard ANSI SQL, qui a inclus cette restriction depuis l'introduction de la récursivité (en 1999 je crois). Il n'y a pas d'accord général sur ce que devrait être la sémantique pour la récursivité surEXCEPT
(également appelée "négation non stratifiée") dans les langages déclaratifs tels que SQL. De plus, il est notoirement difficile (voire impossible) de mettre en œuvre efficacement une telle sémantique (pour des bases de données de taille raisonnable) dans un système SGBDR.
Et il semble que l'implémentation finale ait été faite en 2014 pour les bases de données avec un niveau de compatibilité de 120 ou supérieur .
Les références récursives dans une clause EXCEPT génèrent une erreur en conformité avec la norme SQL ANSI.