Pourquoi les boucles imbriquées ne prennent en charge que les jointures gauches?

11

Dans le blog de Craig Freedman, Nested Loops Join , il explique pourquoi la jointure de boucles imbriquées ne peut pas prendre en charge une jointure externe droite:

Le problème est que nous analysons la table interne plusieurs fois - une fois pour chaque ligne de la jointure externe. Nous pouvons rencontrer les mêmes lignes internes plusieurs fois pendant ces analyses multiples. À quel moment pouvons-nous conclure qu'une rangée intérieure particulière ne s'est pas jointe ou ne se joindra pas?

Quelqu'un peut-il expliquer cela d'une manière vraiment simple et éducative?

Est-ce à dire que la boucle commence par la table externe ( R1) et scanne l'intérieur ( R2)?

Je comprends que pour une R1valeur qui ne rejoint pas R2, elle doit être remplacée par un NULLpour que l'ensemble de résultats devienne ( NULL, R2). Pour moi, il semble impossible de renvoyer une R2valeur lorsque R1ne se joint pas, car il ne peut pas savoir quelle R2valeur renvoyer. Mais ce n'est pas ainsi que cela est expliqué. Ou est-ce?

SQL Server optimise en fait (et remplace souvent) RIGHT JOINavec LEFT JOIN, mais la question est d'expliquer pourquoi il est techniquement impossible pour une logique d' NESTED LOOPS JOINutilisation / de support RIGHT JOIN.

GordonLiddy
la source

Réponses:

12

Le problème principal ici est l'implémentation d'une jointure externe, en utilisant des boucles imbriquées, d'une manière technique opposée à la manière logique , où la table interne est accessible via la boucle externe et la table externe est accessible via la boucle interne .

Étant donné les tableaux A et B, implémentons A LEFT JOIN B.

A
--
1
2

B
_
1
3

Tout d'abord, faisons-le de manière " naturelle ".

Nous parcourons A.
Nous accédons à l'enregistrement 1.
Nous parcourons B.
Nous trouvons l'enregistrement 1 dans B et la sortie 1-1 .

Nous continuons à itérer sur A.
Nous accédons à l' enregistrement 2.
Nous parcourons B.
Nous ne trouvons aucune correspondance dans B.
Nous produisons 2-null .

Maintenant, faisons-le de la manière " opposée ".

Nous parcourons B.
Nous accédons à l'enregistrement 1.
Nous parcourons A.
Nous trouvons l'enregistrement 1 dans A et la sortie 1-1 .

Nous continuons d'itérer à travers B.
Nous accédons à l' enregistrement 3.
Nous parcourons à travers A.
Nous ne trouvons aucune correspondance dans A.

Rappelez-vous maintenant que c'était le cas A LEFT JOIN B, ce qui signifie qu'en plus de 1-1, nous devrions produire 2-null .
Le problème est qu'à ce stade, nous n'avons aucune idée pour quels enregistrements id A nous avons déjà une correspondance (1) et pour quels enregistrements nous n'en avons pas (2).


Cela peut en fait être résolu de diverses manières, par exemple en maintenant un tableau de bits pour la table A.
Lorsqu'un enregistrement A est trouvé comme correspondance, nous le marquons dans le tableau de bits.
À la fin des boucles imbriquées, nous parcourons le tableau de bits et sortons et sortons tout enregistrement qui n'a pas été marqué.
C'est évidemment plus compliqué que la boucle imbriquée "naturelle".

David דודו Markovitz
la source
13

Ce que je n'aime pas dans l'article lié, c'est l'affirmation selon laquelle "l'algorithme de jointure en boucle imbriquée ne prend pas en charge l'opérateur de jointure logique de jointure droite" .

Bien qu'il y ait une limitation, le libellé à ce stade est un peu déroutant. J'espère que ce qui suit explique mieux les choses:

L'algorithme de jointure lop imbriquée implique deux tables (que les tables de base ou les ensembles de résultats des opérations précédentes ne soient pas pertinentes) qui sont nommées table externe et interne et elles sont traitées de manière différente par l'algorithme (la table "externe" est traversée à l'extérieur boucle et la table "intérieure" au niveau des boucles intérieures).

Donc, disons que nous avons une jointure:

A (some_type) JOIN B

L'algorithme peut être exécuté comme:

outer-loop-A  nested-loop  inner-loop-B

ou:

outer-loop-B  nested-loop  inner-loop-A

Maintenant, si ( some_type) est INNERou CROSSjoin, alors il n'y a pas de limitation, le planificateur peut choisir entre l'une des deux façons (avec des caractéristiques de performance différentes, selon la taille des ensembles, la distribution des valeurs des colonnes jointes, des index, etc. Généralement, la plus petite table sera choisie pour être la table "externe" dans l'algorithme).

Mais quand some_typeest LEFTjoin, il ne peut utiliser que:

outer-loop-A  nested-loop  inner-loop-B

et pas

outer-loop-B  nested-loop  inner-loop-A

Et comme un RIGHTpeut toujours être réécrit en tant que LEFTjointure, il a la même limitation, en sens inverse. Pour A RIGHT JOIN B(qui peut être réécrit a B LEFT JOIN A), il ne peut utiliser que:

outer-loop-B  nested-loop  inner-loop-A

et non l'inverse * .

La même limitation s'applique pour la demi-jointure gauche, la demi-jointure gauche, la demi-jointure droite et la demi-jointure droite.

La FULLjointure, d'autre part, ne peut pas être gérée directement avec un algorithme de jointure en boucle imbriquée. L'article explique très bien (c'est vers la fin) comment une jointure complète peut être réécrite (et est par l'optimiseur) en une union d'une jointure gauche et d'une anti-semi-jointure gauche qui pourrait alors être planifiée en deux boucles imbriquées (et un syndicat).

* Comme l'explique Dudu Markovitz dans sa réponse, la voie inverse pourrait être utilisée mais seulement si nous modifions l'algorithme de jointure en boucle imbriquée pour avoir une structure supplémentaire et une étape supplémentaire à la fin.

ypercubeᵀᴹ
la source
Eh bien, cela a beaucoup clarifié. Votre réponse combinée avec Dudu M: s l'explique très bien!
GordonLiddy