Wenn man von anderen Programmiersprachen zu SQL kommt, sieht die Struktur einer rekursiven Abfrage ziemlich seltsam aus. Gehen Sie Schritt für Schritt durch, und es scheint auseinander zu fallen.
Betrachten Sie das folgende einfache Beispiel:
CREATE TABLE #NUMS
(N BIGINT);
INSERT INTO #NUMS
VALUES (3), (5), (7);
WITH R AS
(
SELECT N FROM #NUMS
UNION ALL
SELECT N*N AS N FROM R WHERE N*N < 10000000
)
SELECT N FROM R ORDER BY N;
Lass uns durchgehen.
Zuerst wird das Ankerelement ausgeführt und die Ergebnismenge in R abgelegt. Daher wird R auf {3, 5, 7} initialisiert.
Dann fällt die Ausführung unter UNION ALL und das rekursive Element wird zum ersten Mal ausgeführt. Es wird auf R ausgeführt (d. H. Auf dem R, das wir derzeit in der Hand haben: {3, 5, 7}). Dies führt zu {9, 25, 49}.
Was macht es mit diesem neuen Ergebnis? Wird {9, 25, 49} an die vorhandene {3, 5, 7} angehängt, die resultierende Vereinigung R bezeichnet und die Rekursion von dort fortgesetzt? Oder definiert es R neu, nur dieses neue Ergebnis zu sein {9, 25, 49} und die gesamte Vereinigung später?
Weder Wahl macht Sinn.
Wenn R jetzt {3, 5, 7, 9, 25, 49} ist und wir die nächste Iteration der Rekursion ausführen, erhalten wir {9, 25, 49, 81, 625, 2401} hat {3, 5, 7} verloren.
Wenn R jetzt nur {9, 25, 49} ist, haben wir ein Problem mit der falschen Bezeichnung. Unter R versteht man die Vereinigung der Ankerelement-Ergebnismenge und aller nachfolgenden rekursiven Element-Ergebnismengen. Wobei {9, 25, 49} nur eine Komponente von R ist. Es ist nicht das vollständige R, das wir bisher angehäuft haben. Daher macht es keinen Sinn, das rekursive Element so zu schreiben, dass es aus R auswählt.
Ich weiß auf jeden Fall zu schätzen, was @Max Vernon und @Michael S. unten beschrieben haben. Das heißt, dass (1) alle Komponenten bis zur Rekursionsgrenze oder Nullmenge erstellt werden und dann (2) alle Komponenten zusammen unioniert werden. So verstehe ich, dass die SQL-Rekursion tatsächlich funktioniert.
Wenn wir SQL neu entwerfen würden, würden wir möglicherweise eine klarere und explizitere Syntax durchsetzen, etwa so:
WITH R AS
(
SELECT N
INTO R[0]
FROM #NUMS
UNION ALL
SELECT N*N AS N
INTO R[K+1]
FROM R[K]
WHERE N*N < 10000000
)
SELECT N FROM R ORDER BY N;
Irgendwie ein induktiver Beweis in der Mathematik.
Das Problem mit der aktuellen SQL-Rekursion ist, dass sie verwirrend geschrieben ist. Die Art und Weise, wie es geschrieben steht, besagt, dass jede Komponente durch Auswahl von R gebildet wird, aber es bedeutet nicht, dass das gesamte R, das bisher konstruiert wurde (oder zu sein scheint). Es bedeutet nur die vorherige Komponente.
quelle
Antworten:
Die BOL-Beschreibung von rekursiven CTEs beschreibt die Semantik der rekursiven Ausführung wie folgt:
Jede Ebene hat also nur die Ebene über der gesamten bisher gesammelten Ergebnismenge als Eingabe.
Das Obige ist, wie es logisch funktioniert . Physikalisch rekursive CTEs werden derzeit in SQL Server immer mit verschachtelten Schleifen und einem Stack-Spool implementiert. Dies wird hier und hier beschrieben und bedeutet, dass in der Praxis jedes rekursive Element nur mit der übergeordneten Zeile der vorherigen Ebene und nicht mit der gesamten Ebene arbeitet. Aufgrund der verschiedenen Einschränkungen der zulässigen Syntax in rekursiven CTEs funktioniert dieser Ansatz jedoch.
Wenn Sie das
ORDER BY
aus Ihrer Abfrage entfernen, werden die Ergebnisse wie folgt sortiertDies liegt daran, dass der Ausführungsplan dem folgenden sehr ähnlich ist
C#
NB1: Wie oben durch die Zeit , das erste Kind von Ankerelement
3
wird alle Informationen über seine Geschwister verarbeitet werden,5
und7
, und ihre Nachkommen, bereits von der Spule verworfen worden und ist nicht mehr zugänglich.NB2: Das obige C # hat die gleiche allgemeine Semantik wie der Ausführungsplan, aber der Ablauf im Ausführungsplan ist nicht identisch, da dort die Operatoren in einer Pipeline-Exektionsweise arbeiten. Dies ist ein vereinfachtes Beispiel, um den Kern des Ansatzes zu veranschaulichen. Weitere Informationen zum Plan selbst finden Sie unter den früheren Links.
Anmerkung 3: Die Stapelspule selbst ist anscheinend als nicht eindeutiger Clustered-Index implementiert, wobei die Schlüsselspalte der Rekursionsebene und die Uniqueifiers nach Bedarf hinzugefügt werden ( Quelle ).
quelle
IterateToDepthFirst
-Iterate(seed,rcsv)->PhysIterate(seed,rcsv)
. Nur zur Info. Hervorragende Antwort.Dies ist nur eine (halb) begründete Vermutung und wahrscheinlich völlig falsch. Interessante Frage übrigens.
T-SQL ist eine deklarative Sprache. Vielleicht wird ein rekursiver CTE in eine Cursor-artige Operation übersetzt, bei der die Ergebnisse von der linken Seite von UNION ALL an eine temporäre Tabelle angehängt werden. Dann wird die rechte Seite von UNION ALL auf die Werte auf der linken Seite angewendet.
Wir fügen also zuerst die Ausgabe der linken Seite von UNION ALL in die Ergebnismenge ein, dann die Ergebnisse der rechten Seite von UNION ALL, die auf die linke Seite angewendet wurden, und fügen diese in die Ergebnismenge ein. Die linke Seite wird dann durch die Ausgabe von der rechten Seite ersetzt, und die rechte Seite wird erneut auf die "neue" linke Seite angewendet. Etwas wie das:
Sie können dieses Verhalten im Ausführungsplan für den rekursiven CTE sehen:
Dies ist Schritt 1 oben, in dem die linke Seite von UNION ALL zur Ausgabe hinzugefügt wird:
Dies ist die rechte Seite von UNION ALL, auf der die Ausgabe mit der Ergebnismenge verknüpft ist:
quelle
Die SQL Server-Dokumentation , in der T i und T i + 1 erwähnt werden , ist weder sehr verständlich noch enthält sie eine genaue Beschreibung der tatsächlichen Implementierung.
Die Grundidee ist, dass der rekursive Teil der Abfrage alle vorherigen Ergebnisse betrachtet, jedoch nur einmal .
Es kann hilfreich sein zu sehen, wie andere Datenbanken dies implementieren (um das gleiche Ergebnis zu erzielen ). In der Postgres-Dokumentation heißt es:
In der SQLite-Dokumentation wird auf eine etwas andere Implementierung hingewiesen, und dieser Algorithmus für eine einzelne Zeile ist möglicherweise am einfachsten zu verstehen:
quelle
Ich kenne mich speziell mit DB2 aus, aber der Blick auf die EXPLAIN-Diagramme scheint mit SQL Server identisch zu sein.
Der Plan kommt von hier:
Zeigen Sie es an, indem Sie den Plan einfügen
Das Optimierungsprogramm führt nicht buchstäblich für jede rekursive Abfrage eine Union all aus. Es nimmt die Struktur der Abfrage an und weist den ersten Teil der Union all einem "Ankermitglied" zu. Anschließend durchläuft es die zweite Hälfte der Union all ("rekursives Mitglied" genannt) rekursiv, bis die festgelegten Einschränkungen erreicht sind Wenn die Rekursion abgeschlossen ist, fügt der Optimierer alle Datensätze zusammen.
Das Optimierungsprogramm versteht es lediglich als Vorschlag, eine vordefinierte Operation durchzuführen.
quelle