Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 7 Min.

Common Table Expressions (CTEs)

Sind CTEs elegante Zwischentabellen oder nur temporäre Illusionen?
© EMGenie

Wer regelmäßig mit SQL Server arbeitet, kommt irgendwann an den Punkt, an dem Abfragen länger werden als die Geduld des eigenen Query-Editors. Geschachtelte Unterabfragen, Joins über Joins, Aggregationen in zweiter und dritter Ebene – irgendwann ist der Punkt erreicht, an dem selbst der Compiler leise um Gnade bittet. Oder Abfragen lassen sich nur sinnvoll mit Rekursion löschen. Genau an dieser Stelle betreten CTEs – Common Table Expressions – die Bühne.

Was ist eine CTE überhaupt?

Eine CTE ist eine temporäre, benannte Ergebnismenge, die innerhalb einer einzelnen SQL-Anweisung definiert und verwendet werden kann. Sie verhält sich im Grunde wie eine View auf Zeit – nur eben ohne dass sie dauerhaft in der Datenbank gespeichert wird. Sie ist nur für weitere CTEs (bei Verkettung) oder spätestens für das obligatorische abschließenden DML-Statement verfügbar. Das DML-Statement kann auch ein UPDATE sein, denn ja: Ähnlich wie Views sind CTEs updatefähig.

Die Syntax ist angenehm schlicht:

 

;WITH UmsatzProKunde
 AS
 (
     SELECT 
         CustomerID [KundenNr],
         SUM(TotalDue) [Gesamtumsatz]
     FROM Sales.SalesOrderHeader
     GROUP BY CustomerID
 )
 SELECT 
     c.KundenNr,
     c.Gesamtumsatz
 FROM UmsatzProKunde c
 WHERE c.Gesamtumsatz > 50000;

 

Alternativ könnten die (eindeutigen Namen) der CTE-Spalte auch direkt angegeben werden.

 

WITH UmsatzProKunde ([KundenNr], [Gesaumtumsatz])
 AS
 (
     SELECT 
         CustomerID,
         SUM(TotalDue)
     FROM Sales.SalesOrderHeader
     GROUP BY CustomerID
 )
 SELECT 
     c.KundenNr,
     c.Gesamtumsatz
 FROM UmsatzProKunde c
 WHERE c.Gesamtumsatz > 50000;

 

Die Zeilen zwischen den Klammern definieren die CTE – also das, was du früher wahrscheinlich in eine Unterabfrage gepackt hättest. Danach wird sie behandelt, als wäre sie eine eigene Tabelle. Kein CREATE VIEW, kein temporäres Objekt – einfach nur eine elegante Lesbarkeitsschicht.

Außerdem können CTEs verkettet werden. Dies ermöglicht es, mehrere CTEs zu definieren, wobei eine CTE nur auf die zuvor definierten zugreifen kann. Die DML-Anweisung zum Schluss kann dann auf alle zugreifen.

 

;WITH [cte1] AS
 (
     SELECT [ID], CONCAT([Vorname], [Name]) AS [Fullname] FROM [dbo].[Personenverzeichnis]
 ),
 
 -- Die zwei CTE kann auf die erste CTE zugreifen und bestimmt
 -- die Länge des Namens
 [cte2] AS
 (
     SELECT [ID], LEN([Fullname]) AS [NameLength] FROM [cte1]    
 ),
 
 -- Die dritte CTE kann auf CTE 1 + 2 zugreifen und sucht die
 -- maximale Länge des vollständigen Namens
 [cte3] AS
 (
     SELECT MAX([NameLength]) AS [MaxLength] FROM [cte2]
 ),
 
 -- Die vierte CTE kann auf CTE 1 - 3 zugreifen und stellt
 -- alle für das Endergebnis zusammen
 [cte4] AS
 (
     SELECT [pv].[id],
         [cte1].[Fullname],
         CAST([cte2].[NameLength] AS DECIMAL) AS [NameLength],
         (SELECT [MaxLength] FROM [cte3]) AS [MaxLength]    
     FROM [dbo].[Personenverzeichnis] [pv]
     LEFT JOIN [cte1] ON [pv].[ID] = [cte1].[ID]
     LEFT JOIN [cte2] ON [pv].[ID] = [cte2].[ID]
 ),
 
 -- Die vierte CTE kann auf CTE 1 - 4 zugreifen und
 -- führte die letzte Berechnung durch, indem die Länge
 -- prozentual zur maximalen Länge berechnet wird
 [cteFinal] AS
 (
     SELECT *,
         [NameLength] / [MaxLength] AS [PercentLength]
     FROM [cte4]
 )

 

Die cte4 kann also auf cte1, cte2 und cte3 zugreifen, jedoch nicht auf cteFinal.

Warum nicht einfach eine Subquery?

Klar, das obige Beispiel ließe sich auch mit einer klassischen Unterabfrage lösen. Der Vorteil einer CTE zeigt sich erst, wenn du mehrere logische Teilschritte hast oder dieselbe Ergebnismenge mehrfach verwenden willst. Gerade Entwickler mit Hintergrund in einer höheren Programmiersprache dürfte das ansprechen. Es ist damit mehr als bloße Syntaxkosmetik. Lesbarkeit bedeutet Wartbarkeit, und Wartbarkeit bedeutet Zukunftssicherheit.

Ein schönes Detail: Du kannst mehrere CTEs hintereinander definieren, getrennt durch Kommas. SQL Server führt sie intern nacheinander aus, so als würdest du kleine logische Bausteine stapeln. Aber Achtung! Dabei wird nicht eine CTE erst komplett ausgeführt, bevor es weitergeht, sondern es ist mehr ein Verarbeitungsfluss. Stelle dir also bitte keine „internen temporären Tabellen“ vor, die der Reihe nach befüllt werden. Weiter unten gehe ich auf diesen Aspekt noch einmal ein.

Rekursion

Der eigentliche Zauber beginnt, wenn man rekursive CTEs einsetzt. Sie ermöglichen es, hierarchische Strukturen zu traversieren – etwa Organisationsbäume, Artikelstrukturen oder Dateipfade. Beispiel: Ein klassischer Mitarbeiter-Hierarchiebaum.

 

;WITH [cte]
 AS
 (
     -- Anker der Rekursion
     SELECT *, 0 AS [Level] FROM [dbo].[Personenverzeichnis] WHERE [VorgesetzterID] IS NULL
     
     UNION ALL
 
     -- Rekursion
     SELECT [pv].*, [cte].[Level]+ 1 FROM [dbo].[Personenverzeichnis] [pv] INNER JOIN [cte]
     ON [cte].[ID] = [pv].[VorgesetzterID]
 )
 SELECT * FROM [cte]
 OPTION (MAXRECURSION 10); -- 0 oder 32767 stehen für die maximale Tiefe von 32767

 

Diese Form der Selbstbezüglichkeit ist der Moment, in dem SQL ein klein wenig Lisp-artig wird: Eine Definition, die sich selbst verwendet, bis keine weiteren Datensätze mehr gefunden werden. Das Ergebnis ist eine geordnete Baumstruktur – ganz ohne prozedurale Schleife. Zur Vermeidung von rekursiven Explosionen dient der Hint MAXRECURSION.

Die Sache mit dem Semikolon

CTEs sind einige der wenigen Sprachfeatures, die zwingend ein Semikolon benötigen, und zwar nach der vorherigen Anweisung, wenn es eine solche gibt. Um also sicherzugehen, findet man in vielen Skripten (wie auch hier) rein prophylaktisch ein Semikolon.

Semikolon

Nötig ist es am Ende einer Anweisung in den meisten Fällen nicht. Microsoft hält sich hier einfach nicht an ANSI SQL-92. Die Ausnahmen sind CTEs und die MERGE-Anweisung. Aber: das Schreiben von Semikola zum Abschluss einer T-SQL-Anweisung unterstützt Strukturierung und Lesbarkeit. Der Autor outet sich gern als „Semikolon-Fan“

Performance: Zwischen Magie und Missverständnis

Ein häufiger Irrtum: Viele glauben, eine CTE sei eine Art temporäre Tabelle, die einmal berechnet und dann mehrfach verwendet wird. Falsch!

Eine CTE ist nur syntaktischer Zucker. Sie wird bei jeder Referenz neu aufgelöst, als wäre der Inhalt direkt in die Abfrage eingebettet. Wenn du also dieselbe CTE mehrfach im DML-Statement verwendest, führt das zu mehrfacher Auswertung – und damit potenziell zu Leistungseinbußen.

Wer Performance will, nutzt in solchen Fällen besser eine temporäre Tabelle (#Temp oder ##Temp) oder eine Table-Variable (@Variable).

CTEs glänzen in Struktur und Lesbarkeit, nicht in Wiederverwendbarkeit. Die Ausnahme hier sind rekursive CTEs: Dort wird der rekursive Teil tatsächlich iterativ verarbeitet, Zwischenergebnisse werden also intern gepuffert, bis die Bedingung nicht mehr erfüllt ist.

CTEs und moderne SQL-Server-Features

Mit den aktuelleren SQL-Server-Versionen hat sich an der Funktionsweise von CTEs wenig geändert – ihre Bedeutung aber wächst. In Kombination mit Window Functions, JSON-Ausdrücken oder APPLY-Operatoren lassen sich heute extrem komplexe Auswertungen formulieren, die früher Stored Procedures erfordert hätten.

Ein schönes Beispiel: CTE + Window Function = verständliche Trendanalyse.

 

;WITH Umsaetze AS
 (
     SELECT 
         OrderDate,
         SUM(TotalDue) AS Tagesumsatz
     FROM Sales.SalesOrderHeader
     GROUP BY OrderDate
 )
 SELECT 
     OrderDate,
     Tagesumsatz,
     LAG(Tagesumsatz) OVER (ORDER BY OrderDate) AS Vortag,
     Tagesumsatz - LAG(Tagesumsatz) OVER (ORDER BY OrderDate) AS Differenz
 FROM Umsaetze;

 

Klar strukturiert, gut lesbar – und mächtig genug, um Zeitreihen ohne Unterabfragen zu analysieren.

Fehlerquellen und Stolperfallen

Die häufigsten Missverständnisse in der Praxis:

  • Namenskonflikte: Eine CTE überlagert gleichnamige Tabellen oder Views.
  • Scope: Eine CTE lebt nur für die unmittelbar folgende Anweisung – zum Beispiel kennt sie ein zweites SELECT danach nicht mehr.
  • ORDER BY in der CTE: Nicht erlaubt, ähnlich wie in Views.
  • Mehrfachverwendung: Wie erwähnt führt jede Referenz zu einer erneuten Auswertung.
  • Rekursive Explosion: Eine unendliche Rekursion ohne MAXRECURSION-Begrenzung endet in Fehler 530: „The statement terminated. The maximum recursion 100 has been exhausted.“

 

Vermutlich lässt sich diese Liste, wie üblich bei solchen Listen, noch um einige Punkte erweitern …

Best Practices

Zum Schluss noch ein paar Tipps:

  • Sprechende Namen wählen. Eine CTE ist wie eine lokale Variable – sie sollte klar machen, was sie enthält.
  • Nur so viele CTEs wie nötig. Zu viele verschachtelte Definitionen machen das Lesen schwerer, nicht leichter.
  • Performance testen. Der Optimizer kann zwar erstaunlich gut arbeiten, aber bei mehrfachen Referenzen entstehen Redundanzen.
  • Mit MAXRECURSION arbeiten. Besonders bei Daten mit unklarer Hierarchietiefe.
  • Nicht in Prozeduren stapeln. Wenn du viele CTEs brauchst, ist es vielleicht Zeit, die Logik tatsächlich aufzuteilen.

Fazit

Eine CTE ist kein Performance-Trick und kein Ersatz für temporäre Tabellen. Sie ist eine Lesbarkeitswaffe. Sie hilft, SQL-Abfragen so zu schreiben, dass sie auch in sechs Monaten noch verstanden werden können – nicht nur von Maschinen, sondern von Menschen. CTEs sind der Beweis, dass man selbst in deklarativer Sprache strukturiert denken kann – fast schon ein Stück funktionale Eleganz mitten im relationalen Alltag. Außerdem bändigen sie Rekursionen.

Neueste Beiträge

BRIN-Indizes in PostgreSQL - Indizes & Co. in PostgreSQL, Teil 4
PostgreSQL mit BRIN vertritt die Idee, dass ein Index unvollkommen sein kann, solange er kostengünstig und in großem Maßstab effektiv ist. So entsteht eine pragmatische Optimierung, die Präzision gegen Einfachheit eintauscht – und dabei gewinnt.
6 Minuten
SignalRC WebRTC - Der DDC-Truck, Teil 3
WebRTC ist als Tool ideal geeignet, um Videodaten von RC-Modellen in Echtzeit zu übertragen.
7 Minuten
Räumliche Daten mit PostGIS in PostgreSQL - Indizes & Co. in PostgreSQL, Teil 5
Funktionen wie generierte Spalten, LATERAL-Joins und indexbewusste Operatoren ermöglichen in PostGIS räumliche Abfragen, die auch bei zunehmender Komplexität deklarativ, lesbar und performant bleiben.
6 Minuten

Das könnte Dich auch interessieren

GiST- und SP-GiST-Indizes in PostgreSQL - Indizes & Co. in PostgreSQL, Teil 2
GiST ermöglicht es Indizes in PostgreSQL, Beziehungen wie Überschneidung, Einschluss und Entfernung zu verstehen. SP-GiST hingegen erlaubt es Indizes, saubere Partitionen in hierarchischen oder präfixbasierten Daten auszunutzen.
7 Minuten
Partitionierung - Acht Kostbarkeiten in T-SQL, Teil 4
Daten häppchenweise oder: Was ist Partitionierung und warum?
7 Minuten
26. Jan 2026
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige