Jetzt ist es an der Zeit, über die dunkle Seite der Arbeit mit CTE zu sprechen – die typischen Fehler. Selbst der coolste Query kann kaputtgehen, wenn du diese mächtigen Tools falsch einsetzt. Aber keine Sorge, wir haben für dich eine komplette Anleitung zur Diagnose und Vermeidung dieser Stolperfallen!
1. Fehler: Materialisierung von CTE und ihre Folgen
Eines der wichtigsten Merkmale von PostgreSQL bei der Arbeit mit CTE ist die Standard-Materialisierung. Das bedeutet, dass das Ergebnis des CTE verarbeitet und temporär im Speicher (oder auf der Festplatte, wenn es zu viele Daten sind) gespeichert wird. Wenn es viele Queries gibt oder das Datenvolumen groß ist, kann das die Ausführung deutlich verlangsamen.
Beispiel:
WITH heavy_data AS (
SELECT * FROM large_table
)
SELECT * FROM heavy_data WHERE column_a > 100;
Auf den ersten Blick sieht es so aus, als würde der CTE einfach die Daten filtern. In Wirklichkeit wird aber heavy_data zuerst komplett geladen und materialisiert, und erst danach wird gefiltert. Das kann ewig dauern.
Wie vermeidest du das?
Ab PostgreSQL Version 12 kannst du CTE als Inline-Expression (wie ein Subquery) nutzen, was das Materialisierungsproblem löst. Dafür reicht es, CTEs zu verwenden, die nur einmal gebraucht werden und bei denen keine Zwischenergebnisse gespeichert werden müssen.
Beispiel für einen optimierten Ansatz:
WITH inline_data AS MATERIALIZED (
SELECT * FROM large_table
)
SELECT * FROM inline_data WHERE column_a > 100;
Tipp: Wenn du willst, dass die Materialisierung trotzdem passiert, schreib MATERIALIZED. Wenn nicht – nutze NOT MATERIALIZED.
2. Fehler: Rekursive CTE laufen in Endlosschleifen
Rekursive CTE sind mächtig, aber wenn du keine Begrenzung für die Tiefe der Iterationen setzt, kann das zu Endlosschleifen führen. Das verlangsamt nicht nur die Ausführung, sondern frisst auch alle verfügbaren Ressourcen.
Beispiel:
WITH RECURSIVE endless_loop AS (
SELECT 1 AS wert
UNION ALL
SELECT wert + 1
FROM endless_loop
)
SELECT * FROM endless_loop;
Das Ding erzeugt unendlich viele Zeilen, weil es keine Bedingung gibt, die die Rekursion stoppt.
Wie vermeidest du das?
Füge eine klare Abbruchbedingung mit WHERE hinzu. Zum Beispiel:
WITH RECURSIVE limited_loop AS (
SELECT 1 AS wert
UNION ALL
SELECT wert + 1
FROM limited_loop
WHERE wert < 10
)
SELECT * FROM limited_loop;
Tipp: Wenn du rekursive CTEs für große Hierarchien nutzt, begrenze die Rekursionstiefe mit der PostgreSQL-Option max_recursion_depth.
3. Fehler: Falsche Nutzung von UNION und UNION ALL
Wenn du Basis- und rekursive Queries in einem CTE kombinierst, kann die falsche Wahl zwischen UNION und UNION ALL zu unerwarteten Ergebnissen führen. Zum Beispiel entfernt UNION doppelte Zeilen, was zusätzliche Rechenzeit kostet.
Beispiel:
WITH RECURSIVE employee_hierarchy AS (
SELECT employee_id, manager_id
FROM employees
WHERE manager_id IS NULL
UNION -- Hier wäre UNION ALL besser
SELECT e.employee_id, e.manager_id
FROM employees e
JOIN employee_hierarchy eh ON e.manager_id = eh.employee_id
)
SELECT * FROM employee_hierarchy;
Im Beispiel kann UNION wichtige Zeilen aus der Hierarchie entfernen, wenn sie zufällig doppelt vorkommen. Außerdem wird der Query dadurch langsamer!
Wie behebst du das?
Nutze UNION ALL, wenn du keine doppelten Zeilen entfernen musst:
UNION ALL
4. Fehler: Zu viele CTEs in einem Query
Manche versuchen, ihren Query möglichst strukturiert zu machen und packen dann dutzende CTEs rein. Das macht den Code nicht nur unübersichtlich, sondern überfordert auch den Query Planner von PostgreSQL.
Beispiel:
WITH cte1 AS (...),
cte2 AS (...),
cte3 AS (...),
...
cte20 AS (...)
SELECT ...
FROM cte20;
Sieht aus wie ein Albtraum für jeden Entwickler.
Wie behebst du das?
– Teile den Query in mehrere einfachere auf. Statt einem Mega-Query mit dutzenden CTEs – lieber mehrere unabhängige Queries.
– Noch ein Tipp: Für Zwischenergebnisse, die du mehrfach brauchst, speichere temporäre Tabellen.
5. Fehler: Komplexe CTEs ohne Indexe
Wenn ein CTE mit vielen Daten arbeitet, du aber vergessen hast, Indexe für die Tabellen anzulegen, werden die Queries super langsam. Indexe sind wie Doping für deine Datenbank.
Beispiel:
WITH filtered_data AS (
SELECT * FROM large_table WHERE unindexed_column = 'wert'
)
SELECT * FROM filtered_data;
Wie behebst du das?
Bevor du CTEs nutzt, check, ob deine Tabellen optimiert sind:
CREATE INDEX idx_large_table ON large_table(unindexed_column);
6. Fehler: Versuch, CTE für mehrfachen Datenabruf zu nutzen
Ein CTE wird erstellt, ausgeführt und dann „eingefroren“. Wenn du das Ergebnis an mehreren Stellen brauchst, werden die Daten nicht neu berechnet – das führt manchmal zu Fehlern.
Beispiel:
WITH data AS (
SELECT x, y FROM some_table
)
SELECT x FROM data
WHERE y > 10;
-- Wenn du data nochmal neu berechnen willst, passiert das nicht.
Wie behebst du das?
Wenn du Dynamik oder Neuberechnung brauchst, ist ein CTE vielleicht nicht die beste Wahl. Nutze Subqueries.
7. Fehler: Keine Kommentare
CTEs sind ein mega Tool, aber wer braucht einen komplexen SQL-Query, den niemand – auch du selbst nicht – nach zwei Wochen noch versteht?
Beispiel:
WITH data_filtered AS (
SELECT *
FROM large_table
WHERE some_column > 100
)
SELECT * FROM data_filtered;
Nach einem Monat weiß niemand mehr, warum diese Daten gefiltert wurden!
Kommentiere also deine Queries, besonders wenn du komplexe oder rekursive CTEs nutzt:
WITH data_filtered AS (
-- Filter Daten nach Bedingung: Wert von some_column > 100
SELECT *
FROM large_table
WHERE some_column > 100
)
SELECT * FROM data_filtered;
8. Fehler: CTE-Missbrauch statt temporärer Tabellen
Manchmal sind temporäre Tabellen viel besser geeignet. Zum Beispiel, wenn du das Ergebnis mehrfach in verschiedenen Queries brauchst oder mit riesigen Datenmengen arbeitest.
Beispiel:
WITH temp_data AS (
SELECT * FROM large_table
)
SELECT * FROM temp_data WHERE column_a > 100;
SELECT * FROM temp_data WHERE column_b < 50;
So ein Query mit CTE wird zweimal ausgeführt, obwohl sich die Daten nicht ändern!
Wie behebst du das?
Erstelle eine temporäre Tabelle, wenn du die Daten mehrfach brauchst:
CREATE TEMP TABLE temp_table AS
SELECT * FROM large_table;
SELECT * FROM temp_table WHERE column_a > 100;
SELECT * FROM temp_table WHERE column_b < 50;
Abschließender Tipp
Wie bei jedem mächtigen Feature ist es bei CTE wichtig zu verstehen, dass sie nicht immer das beste Tool für jede Aufgabe sind. Überlege dir, warum und wie du sie einsetzt. Der Ansatz „je mehr CTE, desto besser“ kann die Performance und Lesbarkeit deines Codes echt verschlechtern. Und natürlich: Mach Performance-Tests und optimiere deine Queries regelmäßig.
GO TO FULL VERSION