SQL Academy
Premium

Common Table Expression, der WITH-Operator

Eine Common Table Expression (CTE) ist eine temporäre Ergebnismenge, auf die du in nachfolgenden Queries zugreifen kannst. Für das Schreiben einer CTE wird der Operator WITH verwendet.

MySQL 8.4
-- Beispiel für die Verwendung der WITH-Konstruktion
WITH Aeroflot_trips AS
    (SELECT TRIP.* FROM Company
        INNER JOIN Trip ON Trip.company = Company.id WHERE name = 'Aeroflot')

SELECT plane, COUNT(plane) AS amount FROM Aeroflot_trips GROUP BY plane;

Ein Ausdruck mit WITH gilt als „temporär“, weil das Ergebnis nirgendwo dauerhaft im Datenbankschema gespeichert wird, sondern wie eine temporäre View nur für die Dauer der Query existiert, also nur während der Ausführung der Operatoren SELECT, INSERT, UPDATE, DELETE oder MERGE verfügbar ist. Eine CTE ist nur innerhalb der Query gültig, zu der sie gehört. Das verbessert die Struktur der Query, ohne den globalen Namensraum zu belasten.

Syntax des WITH-Operators

MySQL 8.4
WITH cte_name [(spalte [, ...])] AS (unterabfrage)
    [, cte_name [(spalte [, ...])] AS (unterabfrage)] ...
  • [ ]optionaler Teil
  • ...kann wiederholt werden
cte_name

Der Name der Common Table Expression. Brauchst du mehrere CTEs, trennst du sie durch Kommas.

spalte

Spaltennamen der CTE, durch Kommas getrennt. Lässt du sie weg, werden sie aus der Subquery übernommen.

Optional
unterabfrage

Die Subquery, deren Ergebnis du an anderer Stelle der SQL-Query über den Namen cte_name verwenden kannst.

Unterabfrage

Beispiel-Queries

  1. Wir bauen die CTE Aeroflot_trips, die alle Flüge der Airline „Aeroflot“ enthält.
MySQL 8.4
WITH Aeroflot_trips AS
    (SELECT plane, town_from, town_to FROM Company
        INNER JOIN Trip ON Trip.company = Company.id WHERE name = 'Aeroflot')

SELECT * FROM Aeroflot_trips;
planetown_fromtown_to
IL-86MoscowRostov
IL-86RostovMoscow
  1. Genauso bauen wir die CTE Aeroflot_trips, aber mit umbenannten Spalten.
MySQL 8.4
WITH Aeroflot_trips (aeroflot_plane, town_from, town_to) AS
    (SELECT plane, town_from, town_to FROM Company
        INNER JOIN Trip ON Trip.company = Company.id WHERE name = 'Aeroflot')

SELECT * FROM Aeroflot_trips;
aeroflot_planetown_fromtown_to
IL-86MoscowRostov
IL-86RostovMoscow
  1. Mit dem WITH-Operator definieren wir mehrere CTEs.
MySQL 8.4
WITH Aeroflot_trips AS
    (SELECT TRIP.* FROM Company
        INNER JOIN Trip ON Trip.company = Company.id WHERE name = 'Aeroflot'),
    Don_avia_trips AS
    (SELECT TRIP.* FROM Company
        INNER JOIN Trip ON Trip.company = Company.id WHERE name = 'Don_avia')

SELECT * FROM Don_avia_trips UNION SELECT * FROM  Aeroflot_trips;
idcompanyplanetown_fromtown_totime_outtime_in
11811TU-134RostovMoscow1900-01-01T06:12:00.000Z1900-01-01T08:01:00.000Z
11821TU-134MoscowRostov1900-01-01T12:35:00.000Z1900-01-01T14:30:00.000Z
11871TU-134RostovMoscow1900-01-01T15:42:00.000Z1900-01-01T17:39:00.000Z
11881TU-134MoscowRostov1900-01-01T22:50:00.000Z1900-01-02T00:48:00.000Z
11951TU-154RostovMoscow1900-01-01T23:30:00.000Z1900-01-02T01:11:00.000Z
11961TU-154MoscowRostov1900-01-01T04:00:00.000Z1900-01-01T05:45:00.000Z
11452IL-86MoscowRostov1900-01-01T09:35:00.000Z1900-01-01T11:23:00.000Z
11462IL-86RostovMoscow1900-01-01T17:55:00.000Z1900-01-01T20:01:00.000Z

Rekursion in CTEs

CTEs können auch für rekursive Queries eingesetzt werden, die Daten iterativ verarbeiten — zum Beispiel für hierarchische Datenstrukturen wie „Vorgesetzter — Untergebener“.

Syntax einer rekursiven CTE

Eine rekursive CTE besteht aus zwei Teilen, die durch UNION ALL getrennt werden:

  • Der Startdatensatz ohne rekursive Verweise.
  • Der rekursive Teil: eine Query, die sich auf die CTE selbst bezieht, um die Rekursion fortzusetzen.
MySQL 8.4
WITH RECURSIVE cte_name [(spalte [, ...])] AS (
    -- Startdatensatz
    SELECT ausdruck [, ...]
    FROM tabellen_name
    [WHERE bedingung]

    UNION ALL

    -- Rekursiver Teil
    SELECT ausdruck [, ...]
    FROM cte_name
    INNER JOIN tabellen_name ON verbund_bedingung
    [WHERE bedingung]
)

SELECT * FROM cte_name;
  • [ ]optionaler Teil
  • ...kann wiederholt werden
cte_name

Der Name der CTE. Der rekursive Teil bezieht sich auf ihn, um die Rekursion fortzusetzen.

spalte

Die Spaltennamen der CTE. Ohne Liste werden die Namen aus dem Startdatensatz übernommen.

Optional
ausdruck

Spalten und Berechnungen, die jeder Teil auswählt. Beide Teile müssen gleich viele Spalten liefern. Die Spaltentypen legt der Startdatensatz fest, die Werte des rekursiven Teils müssen dazu passen. Im rekursiven Teil steht oft eine Berechnung wie level + 1.

tabellen_name

Die Tabelle mit den Ausgangsdaten, zum Beispiel Employees.

verbund_bedingung

Wie die Zeilen der Tabelle mit den Zeilen aus dem vorherigen Schritt verknüpft sind, zum Beispiel e.managerId = s.id.

bedingung

Filtert Zeilen: im Startdatensatz, mit welchen Zeilen es losgeht, im rekursiven Teil, welche Zeilen im nächsten Schritt hinzukommen.

Optional

Beispiel: Hierarchie von Vorgesetzten und Untergebenen

Schauen wir uns die Tabelle Employees an, die IDs von Mitarbeitenden und ihren Vorgesetzten enthält:

idnamemanagerId
1John Smith<NULL>
2Michael Johnson1
3Robert Williams1
4James Brown2
5David Jones2
6Richard Davis3

Gesucht sind alle Untergebenen von John Smith (id=1) auf allen Hierarchieebenen.

MySQL 8.4
WITH RECURSIVE Subordinates AS (
    -- Startdatensatz
    SELECT id, name, managerId
    FROM Employees
    WHERE managerId = 1

    UNION ALL

    -- Rekursiver Teil: Untergebene der Untergebenen
    SELECT e.id, e.name, e.managerId
    FROM Employees e
    INNER JOIN Subordinates s ON e.managerId = s.id
)

SELECT * FROM Subordinates;
idnamemanagerId
2Michael Johnson1
3Robert Williams1
4James Brown2
5David Jones2
6Richard Davis3

Ablauf einer rekursiven CTE

  1. Startdatensatz: Alle Mitarbeitenden mit managerId=1 werden ausgewählt (direkte Untergebene von John Smith).
  2. Rekursiver Teil: Für jeden im Startdatensatz ausgewählten Mitarbeiter werden dessen Untergebene geholt (wo managerId gleich der id des ausgewählten Mitarbeiters ist).
  3. Vereinigung: Die Ergebnisse aus Startdatensatz und rekursivem Teil werden mit UNION ALL zusammengeführt.
  4. Rekursion: Der Vorgang wiederholt sich für jede neue Gruppe von Untergebenen, bis alle Hierarchieebenen abgedeckt sind.

Fazit

CTEs wurden in SQL eingeführt, um komplexe, lange Queries — insbesondere mit vielen Subqueries — zu vereinfachen. Ihr Hauptzweck ist, Queries lesbarer, einfacher zu schreiben und besser zu warten zu machen. Das wird erreicht, indem große und komplexe Queries in benannten Ausdrücken versteckt werden, die dann in der Hauptquery verwendet werden.