Zurück zur Artikelliste Artikel
9 Leseminuten

Was ist eine rekursive CTE in SQL?

Der Artikel, der Ihnen praktische Beispiele für die Verwendung rekursiver CTEs in SQL zeigt.

Wenn Sie schon von den rekursiven CTEs in SQL gehört haben, sie aber noch nie benutzt haben, ist dieser Artikel genau das Richtige für Sie. Er ist auch etwas für Sie, wenn Sie nie genug von rekursiven CTE-Beispielen bekommen können.

Bevor wir uns mit der Rekursion befassen, werde ich Sie daran erinnern, was CTEs sind und was ihre Syntax ist. Dann werde ich das Gleiche für rekursive CTEs tun. Danach zeige ich Ihnen anhand von drei Beispielen, wie rekursive CTEs funktionieren.

Was sind CTEs?

Die CTE (common table expression), auch bekannt als WITH Klausel, ist eine SQL-Funktion, die einen temporären Datensatz zurückgibt, der von einer anderen Abfrage verwendet werden kann. Da es sich um ein temporäres Ergebnis handelt, wird es nirgendwo gespeichert, aber es kann dennoch wie jede andere Tabelle referenziert werden.

Es gibt zwei Arten von CTEs, nicht rekursive und rekursive.

Hier ist ein guter Artikel, der Ihnen zeigt, was CTEs sind und wie sie funktionieren.

Nicht-rekursive CTE-Syntax

Die allgemeine Syntax einer nicht rekursiven CTE sieht wie folgt aus:

WITH cte_name AS (cte_query_definition)

SELECT *
FROM   cte_name;

Der erste Teil der Syntax ist das CTE. Er beginnt mit dem Schlüsselwort WITH. Dann geben Sie Ihrem CTE einen Namen. Danach folgt das Schlüsselwort AS, und in den Klammern können Sie das CTE definieren.

Der zweite Teil der Syntax ist eine einfache SELECT -Anweisung. Sie wird unmittelbar nach dem rekursiven CTE geschrieben, ohne Kommas, Semikolons oder ähnliche Zeichen. Wie ich bereits sagte, wird die CTE in einer anderen Abfrage wie jede andere Tabelle verwendet. Das ist genau das, was die Anweisung SELECT tut.

Hier ist der Artikel, der Ihnen zusätzlich mit der CTE-Syntax und ihren Regeln helfen kann. Und wenn Sie weitere CTE-Beispiele benötigen, ist dieser Artikel genau das Richtige für Sie.

Rekursive CTE-Syntax

Ein rekursiver CTE verweist auf sich selbst. Sie gibt eine Teilmenge der Ergebnisse zurück, verweist dann wiederholt (rekursiv) auf sich selbst und hört auf, wenn sie alle Ergebnisse zurückgibt.

Die Syntax für eine rekursive CTE unterscheidet sich nicht allzu sehr von der einer nicht rekursiven CTE:

WITH RECURSIVE cte_name AS (
    cte_query_definition (the anchor member)

    UNION ALL

    cte_query_definition (the recursive member)
)


SELECT *
FROM   cte_name;

Auch hier steht am Anfang des CTE die Klausel WITH. Wenn Sie jedoch möchten, dass Ihre CTE rekursiv ist, dann schreiben Sie nach WITH das Schlüsselwort RECURSIVE. Dann geht es weiter wie gewohnt: AS wird von den Klammern mit der CTE-Abfragedefinition gefolgt. Diese erste Abfragedefinition wird als Ankerelement bezeichnet.

Um das Ankerelement mit dem rekursiven Element zu verbinden, müssen Sie den Befehl UNION oder UNION ALL verwenden. Das rekursive Mitglied ist natürlich der rekursive Teil von CTE, der auf das CTE selbst verweist. Wie das funktioniert, werden Sie gleich in einem Beispiel sehen.

Rekursive CTEs werden vor allem dann verwendet, wenn Sie hierarchische Daten oder Diagramme abfragen möchten. Dabei kann es sich um die Organisationsstruktur eines Unternehmens, einen Familienstammbaum, eine Restaurantkarte oder verschiedene Routen zwischen Städten handeln. In diesen Artikeln erfahren Sie, wie CTEs mit hierarchischen Strukturen arbeiten und wie man Graphdaten abfragt.

Nachdem wir nun verstanden haben, wie rekursive CTEs funktionieren, wollen wir uns einige Beispiele ansehen.

Beispiel 1 - Suche nach Vorgesetzten und Hierarchiestufen für alle Mitarbeiter

Für dieses Problem verwende ich Daten aus der Tabelle employeesdie die folgenden Spalten enthält:

  • id: Die ID des Mitarbeiters.
  • first_name: Der Vorname des Mitarbeiters.
  • last_name: Nachname des Mitarbeiters.
  • boss_id: Die ID des Vorgesetzten des Mitarbeiters.

So sehen die Daten aus:

idfirst_namelast_nameboss_id
1DomenicLeaver5
2ClevelandHewins1
3KakalinaAtherton8
4RoxannaFairlieNULL
5HermieComsty4
6PoohGoss8
7FaulknerChalliss5
8BobbeBlakeway4
9LaureneBurchill1
10AugustaGosdin8

Es ist nicht allzu kompliziert. Zum Beispiel ist der Chef von Domenic Leaver der Mitarbeiter mit der ID 5; das ist Hermie Comsty. Das gleiche Prinzip gilt für alle anderen Mitarbeiter, außer für Roxanna Fairlie. Sie hat keinen Chef; in der Spalte boss_id steht der Wert NULL. Daraus können wir schließen, dass Roxanna der Präsident oder Eigentümer der Firma ist.

Schreiben wir nun die rekursive CTE, um alle Mitarbeiter und ihre direkten Vorgesetzten aufzulisten.

WITH RECURSIVE company_hierarchy AS (
  SELECT	id,
    		first_name,
    		last_name,
    		boss_id,
		0 AS hierarchy_level
  FROM employees
  WHERE boss_id IS NULL

  UNION ALL 
  
  SELECT	e.id,
    		e.first_name,
    		e.last_name,
    		e.boss_id, 
		hierarchy_level + 1
  FROM employees e, company_hierarchy ch
  WHERE e.boss_id = ch.id
)

SELECT   ch.first_name AS employee_first_name,
	   ch.last_name AS employee_last_name,
	   e.first_name AS boss_first_name,
	   e.last_name AS boss_last_name,
	   hierarchy_level
FROM company_hierarchy ch
LEFT JOIN employees e
ON ch.boss_id = e.id
ORDER BY ch.hierarchy_level, ch.boss_id;

Was macht diese Abfrage? Es ist eine rekursive Abfrage, also beginnt sie mit WITH RECURSIVE. Der Name der CTE ist company_hierarchy. Nach AS steht die CTE-Definition in Klammern.

Die erste Anweisung SELECT wählt alle employee Tabellenspalten aus, in denen die Spalte boss_id den Wert NULL hat. Kurz gesagt, sie wählt Roxanna Fairlie aus, weil nur sie den Wert NULL in dieser Spalte hat. Noch kürzer: Ich beginne die Rekursion an der Spitze der Organisationsstruktur. Es gibt auch eine Spalte hierarchy_level mit dem Wert 0. Das bedeutet, dass die Ebene des Eigentümers/Präsidenten 0 ist - er befindet sich an der Spitze der Hierarchie.

Ich habe die UNION ALL verwendet, um diese SELECT-Anweisung mit der zweiten zu verbinden, d. h. mit dem rekursiven Mitglied. Im rekursiven Mitglied wähle ich alle Spalten aus der Tabelle employees und die CTE company_hierarchy, wobei die Spalte boss_id gleich der Spalte id ist. Beachten Sie den Teilhierarchy_level + 1. Das bedeutet, dass die CTE bei jeder Rekursion 1 zur vorherigen Hierarchieebene hinzufügt, und zwar so lange, bis sie das Ende der Hierarchie erreicht. Beachten Sie auch, dass ich diese CTE wie jede andere Tabelle behandle. Um die Definition der CTE zu beenden, schließen Sie einfach die Klammern.

Schließlich gibt es noch eine dritte Anweisung SELECT, die sich außerhalb der CTE befindet. Sie wählt die Spalten aus, in denen die Mitarbeiter, die Namen ihrer Vorgesetzten und die Hierarchieebene angezeigt werden sollen. Die Daten werden aus der CTE und der Tabelle employees. Ich habe diese beiden mit LEFT JOIN verknüpft, da ich alle Daten aus der CTE haben möchte - einschließlich Roxanna Fairlie, die den Wert NULL in der Spalte boss_id hat. Das Ergebnis wird in aufsteigender Reihenfolge angezeigt: zuerst nach der Hierarchieebene, dann nach der ID des Vorgesetzten. So sieht es aus:

employee_first_nameemployee_last_nameboss_first_nameboss_last_namehierarchy_level
RoxannaFairlieNULLNULL0
HermieComstyRoxannaFairlie1
BobbeBlakewayRoxannaFairlie1
DomenicLeaverHermieComsty2
FaulknerChallissHermieComsty2
AugustaGosdinBobbeBlakeway2
PoohGossBobbeBlakeway2
KakalinaAthertonBobbeBlakeway2
LaureneBurchillDomenicLeaver3
ClevelandHewinsDomenicLeaver3

Roxanna Fairlie ist die oberste Chefin; das wussten Sie bereits. Es gibt zwei Mitarbeiter auf Ebene 1. Das heißt, Bobbe Blakeway und Hermie Comsty sind Roxanna Fairlie direkt unterstellt. Auf Ebene 2 gibt es Angestellte, deren direkte Vorgesetzte Bobbe Blakeway und Hermie Comsty sind. Es gibt auch eine dritte Ebene in der Hierarchie. Das sind die Mitarbeiter, deren direkter Vorgesetzter Domenic Leaver ist.

Beispiel 2 - Ermittlung des Investitionsbetrags nach Investor

In diesem Beispiel verwende ich die Tabelle investment:

  • id: Die ID der Investition.
  • investment_amount: Der Betrag der Investition.

Die Daten in der Tabelle sehen wie folgt aus:

idinvestment_amount
19,705,321.00
25,612,948.60
35,322,146.00

Dies sind die Beträge der drei möglichen Investitionsoptionen. Sie werden von den drei Investoren berücksichtigt, die den gesamten Investitionsbetrag in gleiche Teile aufteilen werden. Ihre Aufgabe ist es, den Betrag pro Investor in Abhängigkeit von der Anzahl der Investoren zu berechnen, d. h. ob ein, zwei, drei oder kein Investor in jede Anlage investiert.

Die Abfrage, mit der dieses Problem gelöst wird, lautet:

WITH RECURSIVE per_investor_amount AS (
	SELECT	0 AS investors_number,
			0.00 AS investment_amount,
			0.00 AS individual_amount
	UNION 

	SELECT	investors_number + 1,
			i.investment_amount,
			i.investment_amount / (investors_number + 1)
	FROM investment i, per_investor_amount pia
	WHERE investors_number << 3
)

SELECT *
FROM per_investor_amount
ORDER BY  investment_amount, investors_number;

Auch hier beginnt die CTE mit WITH RECURSIVE, gefolgt von ihrem Namen und der Abfragedefinition. Dieses Mal verwende ich das Ankerelement der rekursiven Abfrage, um einige Daten zu erstellen. Die Spalten sind investors_number, investment_amount und individual_amount. Dies ist der Punkt, an dem die Rekursion beginnen soll (genauso wie im vorherigen Beispiel mit hierarchy_level = 0).

Dann kommen UNION und das rekursive Mitglied. Dieser Teil der Abfrage wird die Spalte investors_number bei jeder Rekursion um eins erhöhen. Dies geschieht für jede investment_amount. In der dritten Spalte wird der Betrag dieser Investition pro Anleger berechnet, abhängig von der Anzahl der beteiligten Anleger. Die Rekursion wird für bis zu drei Anleger durchgeführt (d. h. bis die Bedingung WHERE investors_number < 3 erreicht ist).

Danach folgt die einfache SELECT-Anweisung, die alle Spalten aus dem CTE zurückgibt. Und hier ist das Ergebnis:

investors_numberinvestment_amountindividual_amount
00.000.00
15,322,146.005,322,146.00
25,322,146.002,661,073.00
35,322,146.001,774,048.67
15,612,948.605,612,948.60
25,612,948.602,806,474.30
35,612,948.601,870,982.87
19,705,321.009,705,321.00
29,705,321.004,852,660.50
39,705,321.003,235,107.00

Es ist nicht schwer, es zu analysieren. Wenn es keine Investoren gibt, ist der Investitionsbetrag gleich Null, ebenso wie der individuelle Betrag. Wenn die Investition 5.322.146,00 beträgt und es nur einen Anleger gibt, dann ist der Betrag pro Anleger 5.322.146,00. Wenn zwei Anleger den gleichen Betrag investieren, muss jeder von ihnen 2.661.073,00 zahlen. Wenn sich alle drei Anleger für eine Investition entscheiden, zahlt jeder von ihnen 1.774.048,67. Die beiden anderen Investitionsbeträge folgen demselben Muster, wie Sie in der Tabelle sehen können.

Beispiel 3 - Suche nach Strecken zwischen Städten

Im dritten Beispiel verwende ich die Tabelle cities_route, die Daten über niederländische Städte enthält:

  • city_from: Die Abfahrtsstadt.
  • city_to: Die Zielstadt.
  • distance: Die Entfernung zwischen zwei Städten, in Kilometern.
city_fromcity_todistance
GroningenHeerenveen61.4
GroningenHarlingen91.6
HarlingenWieringerwerf52.3
WieringerwerfHoorn26.5
HoornAmsterdam46.1
AmsterdamHaarlem30
HeerenveenLelystad74
LelystadAmsterdam57.2

Verwenden Sie diese Tabelle, um alle möglichen Routen von Groningen nach Haarlem zu finden, mit Angabe der Städte auf der Route und der Gesamtentfernung.

Hier ist die Abfrage zur Lösung dieses Problems:

WITH RECURSIVE possible_route AS (
	SELECT	cr.city_to,
       		cr.city_from || '->' ||cr.city_to AS route,
       		cr.distance
      FROM cities_route cr
      WHERE cr.city_from = 'Groningen'

UNION ALL

SELECT 	cr.city_to,
       		pr.route || '->' || cr.city_to AS route,
        		CAST((pr.distance + cr.distance) AS DECIMAL(10, 2))
      FROM possible_route pr
INNER JOIN cities_route cr
      		ON cr.city_from = pr.city_to
)

SELECT 	pr.route,
		pr.distance
FROM possible_route pr
WHERE pr.city_to = 'Haarlem'
ORDER BY pr.distance;

Schauen wir uns an, was diese Abfrage macht. Die erste Anweisung SELECT in der CTE-Definition wählt die Spalten aus der Tabelle cities_route aus, wobei die Ausgangsstadt Groningen ist. Beachten Sie, dass es auch eine neue Spalte mit dem Namen route gibt, die ich verwenden werde, um die Städte auf der Route zu verketten.

Die UNION ALL verbindet dies mit dem rekursiven Mitglied. Diese SELECT Anweisung wählt die Ankunftsstadt aus, verkettet die Städte auf der Route und addiert schließlich die Entfernungen zwischen diesen Städten zur Gesamtsumme der Route zwischen Groningen und Haarlem. Um all dies zu erreichen, habe ich die CTE mit der Tabelle cities_route.

Dann kommt die Anweisung SELECT, die Daten aus der CTE abruft. Sie wählt die Strecke und die Entfernung aus, wenn die Ankunftsstadt Haarlem ist, wobei die Daten nach der Entfernung in aufsteigender Reihenfolge geordnet sind.

Das Abfrageergebnis sieht wie folgt aus:

routedistance
Groningen->Heerenveen->Lelystad->Amsterdam->Haarlem222.6
Groningen->Harlingen->Wieringerwerf->Hoorn->Amsterdam->Haarlem246.5

Es ist nicht schwer, diese Tabelle zu verstehen. Es gibt zwei Strecken von Groningen nach Haarlem. Sie beinhalten verschiedene Städte dazwischen und sind 222,6 km bzw. 246,5 km lang.

Wenn Sie weiter lernen wollen, sehen Sie sich an, wie Sie eine rekursive CTE anstelle einer langen SQL-Abfrage verwenden können. Und nachdem Sie sich mit diesem Thema beschäftigt haben, machen Sie sich einen Spaß daraus, etwas mit einer rekursiven CTE zu zeichnen.

Rekursive CTEs weiter üben

Diese drei Beispiele haben die Möglichkeiten von rekursiven CTEs in SQL gezeigt. Jetzt ist es an der Zeit, das Gelernte zu vertiefen.

Am besten probieren Sie es mit unserem KursRekursive Abfragen . Er bietet Ihnen eine Fülle von Beispielen, Erklärungen und Übungsmöglichkeiten. Der Kurs ist Teil der Kursreihe Fortgeschrittenes SQL , in der Sie weitere fortgeschrittene SQL-Themen wie Fensterfunktionen, GROUP BY-Erweiterungen und rekursive Abfragen kennenlernen können.

Viel Spaß!