#datenbank Kontext: Datenbanken = ==Common Table Expression== >`WITH`-Block in SQL-Abfrage ➡️[[#Zusammenfassung]] --- Im Folgenden bekommst du eine umfassende, aber einsteigerfreundliche Einführung in CTEs („Common Table Expressions“) im Kontext von Datenbanken – mit Definition, Abgrenzung, Nutzen, Herausforderungen und praxisnahen Beispielen. --- ## 1. Grundidee: Was ist eine CTE? **CTE** steht für **Common Table Expression**. Vereinfacht gesagt ist eine CTE: > Eine **temporäre, benannte Ergebnismenge**, die du in einer SQL-Abfrage definierst und im Anschluss in derselben Abfrage wie eine Tabelle verwenden kannst. Man kann sich das vorstellen wie: - „Ich speichere mir ein Zwischenergebnis unter einem Namen…“ - „…und benutze dieses Zwischenergebnis dann in der eigentlichen Anfrage.“ Grundform: ```sql WITH name_der_cte AS ( -- irgendeine SELECT-Abfrage SELECT ... FROM ... WHERE ... ) SELECT * FROM name_der_cte; ``` Wichtig: - Die CTE existiert **nur für diese eine Abfrage**. - Sie wird **innerhalb der Abfrage** definiert und direkt **danach** genutzt. - Man kann CTEs **mehrfach referenzieren**, als wären es echte Tabellen. --- ## 2. Ein einfaches Beispiel Stell dir vor, du hast eine Tabelle `bestellungen`: - `id` - `kunde_id` - `betrag` - `datum` Du möchtest: 1. Alle Bestellungen des Jahres 2024 herausfiltern. 2. Dann auf Basis dieser gefilterten Daten den Gesamtumsatz pro Kunde berechnen. Ohne CTE könnte man das mit einer verschachtelten Abfrage lösen; mit CTE sieht es lesbarer aus: ```sql WITH bestellungen_2024 AS ( SELECT * FROM bestellungen WHERE datum >= '2024-01-01' AND datum < '2025-01-01' ) SELECT kunde_id, SUM(betrag) AS umsatz_2024 FROM bestellungen_2024 GROUP BY kunde_id; ``` Die CTE `bestellungen_2024` ist hier: „Alle Bestellungen aus 2024“, und wird anschließend in der Hauptabfrage verwendet. --- ## 3. Abgrenzung zu ähnlichen oder verwandten Begriffen ### 3.1 CTE vs. Unterabfrage (Subquery / Derived Table) **Unterabfrage**: Eine Abfrage innerhalb einer anderen Abfrage, z. B.: ```sql SELECT kunde_id, SUM(betrag) AS umsatz_2024 FROM ( SELECT * FROM bestellungen WHERE datum >= '2024-01-01' AND datum < '2025-01-01' ) AS b2024 GROUP BY kunde_id; ``` Unterschiede: - **CTE**: Wird am Anfang mit `WITH` definiert, trägt einen Namen und kann **mehrfach** benutzt werden. - **Subquery**: Steht direkt im `FROM` oder `WHERE`, ist meist **anonymer** und schwerer zu lesen, vor allem bei komplexen Konstrukten. Funktional können CTEs und Subqueries oft das Gleiche – CTEs verbessern eher **Struktur und Lesbarkeit**. --- ### 3.2 CTE vs. View (Sicht) **View** (Sicht) ist wie eine **gespeicherte Abfrage** in der Datenbank: ```sql CREATE VIEW bestellungen_2024 AS SELECT * FROM bestellungen WHERE datum >= '2024-01-01' AND datum < '2025-01-01'; ``` Dann kannst du schreiben: ```sql SELECT kunde_id, SUM(betrag) FROM bestellungen_2024 GROUP BY kunde_id; ``` Unterschiede: - **View**: - Wird **dauerhaft** in der Datenbank definiert. - Hat einen Namen, kann von **vielen Abfragen** und auch anderen Nutzer:innen verwendet werden. - Ändert sich nur über `CREATE OR REPLACE VIEW` / `ALTER VIEW`. - **CTE**: - Gilt **nur für eine einzige Abfrage**. - Ist daher ideal für **einmalige** oder **sehr spezialisierte** Zwischenschritte. - Erfordert keine Rechte zum Erstellen von Objekten in der Datenbank (kein `CREATE VIEW`). Kurz: Views = dauerhafte, wiederverwendbare Bausteine. CTEs = temporäre, einmalige Bausteine innerhalb einer Abfrage. --- ### 3.3 CTE vs. temporäre Tabelle Viele Datenbanken kennen **temporäre Tabellen**, z. B. `#temp_tab` in SQL Server oder `CREATE TEMP TABLE` in PostgreSQL: ```sql CREATE TEMP TABLE bestellungen_2024 AS SELECT * FROM bestellungen WHERE datum >= '2024-01-01' AND datum < '2025-01-01'; SELECT kunde_id, SUM(betrag) FROM bestellungen_2024 GROUP BY kunde_id; ``` Unterschiede: - **Temporäre Tabelle**: - Wird physisch (zumindest logisch) in der Datenbank angelegt. - Existiert für die Dauer einer Session oder Transaktion. - Kann **indiziert** werden (Index hinzufügen) und so bei großen Datenmengen Performancevorteile bringen. - **CTE**: - Keine echte Tabelle, eher eine „logische Abfragekomponente“. - Keine eigenen Indexe. - Gilt nur innerhalb einer Abfrage. --- ### 3.4 CTE vs. Stored Procedure / Function **Stored Procedures** und **Functions** sind Programmierbausteine auf Datenbankseite, z. B.: - Prozeduren: führen mehrere Schritte, ggf. mit Kontrollstrukturen aus (IF, WHILE,…). - Funktionen: geben einen Wert oder eine Tabelle zurück. Unterschiede: - CTE ist **Teil einer einzelnen SQL-Select/Insert/Update/Delete-Abfrage**. - Stored Proc / Function ist **Code**, den man **speichert, versioniert und immer wieder aufrufen** kann. CTEs können innerhalb von Stored Procedures verwendet werden – sie sind eher Bausteine **auf Abfrage-Ebene**, nicht auf Programm-Ebene. --- ## 4. Welche Probleme werden durch CTEs gelöst? ### 4.1 Bessere Lesbarkeit und Struktur Statt eine riesige, komplexe Abfrage mit vielen verschachtelten Unterabfragen zu schreiben, kann man sich die Abfrage in **logische Schritte** zerlegen: 1. `WITH schritt1 AS (...)` 2. `, schritt2 AS (...)` 3. `SELECT ... FROM schritt2 ...` Beispiel: Du möchtest erst „aktive Kunden“ bestimmen und dann nur deren Bestellungen summieren: ```sql WITH aktive_kunden AS ( SELECT id, name FROM kunden WHERE status = 'aktiv' ), bestellungen_aktive_kunden AS ( SELECT b.kunde_id, SUM(b.betrag) AS umsatz FROM bestellungen b JOIN aktive_kunden k ON k.id = b.kunde_id GROUP BY b.kunde_id ) SELECT k.name, bak.umsatz FROM bestellungen_aktive_kunden bak JOIN aktive_kunden k ON k.id = bak.kunde_id; ``` Jeder CTE beschreibt einen **klaren Teilschritt**, das erleichtert Verstehen und Warten des Codes. --- ### 4.2 Wiederverwendung innerhalb einer Abfrage Oft brauchst du ein bestimmtes Zwischenergebnis **mehrmals in derselben Abfrage**. Ohne CTE müsstest du: - Dieselbe Unterabfrage mehrfach schreiben (redundant, fehleranfällig) - Oder du machst Temp-Tabellen / Views, die aber mehr Setup erfordern. Mit CTE definierst du das einmal und verwendest es mehrfach: ```sql WITH bestellungen_2024 AS ( SELECT * FROM bestellungen WHERE datum >= '2024-01-01' AND datum < '2025-01-01' ) SELECT (SELECT COUNT(*) FROM bestellungen_2024) AS anzahl_gesamt, (SELECT SUM(betrag) FROM bestellungen_2024) AS umsatz_gesamt; ``` --- ### 4.3 Schrittweise Transformationen (ETL-artige Abläufe) Man kann komplexe Datenverarbeitungen in **mehrere CTE-Schritte** aufteilen, z. B.: 1. Rohdaten bereinigen (ungültige Werte rausfiltern) 2. Daten anreichern (Join mit Lookup-Tabellen) 3. Aggregationen berechnen 4. Ergebnis selektieren Schema: ```sql WITH raw_data AS ( SELECT * FROM import_tabelle ), cleaned_data AS ( SELECT * FROM raw_data WHERE wert IS NOT NULL ), enriched_data AS ( SELECT c.*, l.beschreibung FROM cleaned_data c LEFT JOIN lookup l ON c.code = l.code ), aggregated AS ( SELECT beschreibung, COUNT(*) AS anzahl, AVG(wert) AS durchschnitt FROM enriched_data GROUP BY beschreibung ) SELECT * FROM aggregated ORDER BY anzahl DESC; ``` Solche „linearen“ CTE-Ketten sind sehr hilfreich, um komplexe ETL-Schritte innerhalb einer SQL-Abfrage klar zu definieren. --- ### 4.4 Rekursive Abfragen (Hierarchien, Bäume, Graphen) Ein **besonderer Typ** von CTE ist die **rekursive CTE**. Damit kann man Strukturen abfragen, die **hierarchisch** sind, z. B.: - Mitarbeiter und ihre Vorgesetzten (Organigramm) - Kategorien und Unterkategorien (Baumstrukturen) - Stücklisten (Bauteil besteht aus Unterteilen, die wiederum Unterteile haben) - Graphen/Netzwerke mit Verbindungen Grundidee: - Es gibt eine **Anker-Abfrage** (Startpunkt). - Und eine **rekursive Abfrage**, die sich immer wieder selbst referenziert. Beispiel: Mitarbeiter-Hierarchie Tabelle `mitarbeiter`: - `id` - `name` - `chef_id` (Verweis auf `id` in derselben Tabelle, NULL für Chef ganz oben) ```sql WITH RECURSIVE hierarchie AS ( -- 1) Anker: Chef (oberste Ebene) SELECT id, name, chef_id, 0 AS ebene FROM mitarbeiter WHERE chef_id IS NULL UNION ALL -- 2) Rekursiver Teil: alle Mitarbeiter, die einem bereits gefundenen Mitarbeiter unterstellt sind SELECT m.id, m.name, m.chef_id, h.ebene + 1 AS ebene FROM mitarbeiter m JOIN hierarchie h ON m.chef_id = h.id ) SELECT * FROM hierarchie ORDER BY ebene, id; ``` Ergebnis: Alle Mitarbeiter mit einer Spalte `ebene`, die angibt, wie weit sie vom obersten Chef entfernt sind. **Ohne rekursive CTEs** wäre das sehr umständlich oder gar nicht (standardkonform) direkt in SQL möglich. Manche Systeme haben dafür eigene Syntax (z. B. `CONNECT BY` in Oracle), rekursive CTEs sind der **SQL-Standard**-Weg. --- ## 5. Weitere praxisnahe Beispiele ### 5.1 Alltagsszenario: „Top-Produkte pro Monat“ Gegeben: - `verkaeufe` mit Spalten: - `produkt_id` - `datum` - `menge` - `umsatz` Ziel: Pro Monat die **Top 3 Produkte nach Umsatz** anzeigen. Ein möglicher Weg mit CTE: ```sql WITH monatliche_umsaetze AS ( SELECT DATE_TRUNC('month', datum) AS monat, produkt_id, SUM(umsatz) AS umsatz_monat FROM verkaeufe GROUP BY DATE_TRUNC('month', datum), produkt_id ), ranking AS ( SELECT monat, produkt_id, umsatz_monat, ROW_NUMBER() OVER ( PARTITION BY monat ORDER BY umsatz_monat DESC ) AS rang_im_monat FROM monatliche_umsaetze ) SELECT monat, produkt_id, umsatz_monat, rang_im_monat FROM ranking WHERE rang_im_monat <= 3 ORDER BY monat, rang_im_monat; ``` Die Logik wird nachvollziehbar in zwei Schritte aufgeteilt: 1. summieren pro Monat und Produkt 2. pro Monat ranken und Top 3 auswählen --- ### 5.2 Kalender-CTE: fehlende Tage auffüllen Manchmal hat man z. B. nur Daten für Tage, an denen Verkäufe stattfanden, möchte aber **jeden Tag im Zeitraum** sehen (auch wenn Umsatz 0 ist). Mit einer rekursiven CTE kann man einen Kalender erzeugen. Beispiel (PostgreSQL-Syntax): ```sql WITH RECURSIVE kalender AS ( SELECT DATE '2024-01-01' AS tag UNION ALL SELECT tag + INTERVAL '1 day' FROM kalender WHERE tag < DATE '2024-01-31' ) SELECT k.tag, COALESCE(SUM(v.umsatz), 0) AS umsatz FROM kalender k LEFT JOIN verkaeufe v ON DATE(v.datum) = k.tag GROUP BY k.tag ORDER BY k.tag; ``` So erhältst du für **jeden Tag im Januar 2024** einen Umsatzwert – bei Tagen ohne Verkäufe eben 0. --- ## 6. Welche Herausforderungen und Stolpersteine gibt es? ### 6.1 Performance (Leistungsfähigkeit) CTEs sind primär ein **Lesbarkeits-Feature**, aber sie können die Performance beeinflussen – je nach Datenbank: - Einige Datenbanken „materialisieren“ CTEs: - Das Zwischenergebnis wird wirklich berechnet und zwischengespeichert. - Mehrfachzugriff ist dann ggf. schneller, aber die Initialberechnung kann teurer sein. - Andere optimieren CTEs ähnlich wie Unterabfragen: - Sie werden im Optimizer „eingefaltet“, d. h. wie eine direkte Teilabfrage behandelt. Typische Punkte: - **Mehrfache Verwendung großer CTEs**: - Wenn eine CTE sehr groß ist und du mehrfach darauf zugreifst, kann das teuer werden. - In manchen Systemen ist dann eine temporäre Tabelle mit Index effizienter. - **Rekursive CTEs**: - Können viel CPU und Zeit brauchen, wenn die Hierarchie sehr tief oder stark verzweigt ist. - Es gibt oft eine maximale Rekursionstiefe (z. B. 100 oder 32767), die begrenzt, wie tief die Rekursion geht. Praxis-Tipp: - CTEs sind super für mittlere Komplexität und moderate Datenmengen. - Bei sehr großen Datenmengen und Performanceproblemen: - Ausführungsplan anschauen. - Mit Temp-Tabellen, Indizes oder Views experimentieren. --- ### 6.2 Übermäßige Verschachtelung Wenn man CTEs zu exzessiv nutzt, z. B. 20 oder mehr CTEs in einer Abfrage, wird es: - schwer zu lesen - schwierig zu debuggen - komplex zu warten Hier gilt: **Abstraktion, wo sinnvoll, aber nicht übertreiben.** Manchmal ist es besser: - Teile der Logik in eine View zu legen, - oder in eine Stored Procedure / Function, - oder die Logik in mehrere kleinere Abfragen aufzuteilen. --- ### 6.3 Datenbankspezifische Unterschiede Nicht jede Datenbank: - unterstützt CTEs gleich - oder hat genau dieselbe Syntax. Beispiele: - **PostgreSQL**: Unterstützt `WITH` und `WITH RECURSIVE`. - **SQL Server**: Unterstützt CTEs, Rekursion ohne extra `RECURSIVE`-Keyword. - **MySQL**: Ab Version 8.0 gibt es CTEs, rekursiv mit `WITH RECURSIVE`. - **Oracle**: Unterstützt CTEs, hatte aber historisch eigene Hierarchie-Syntax (`CONNECT BY`). Wenn du in mehreren Systemen arbeitest, lohnt ein Blick in die Dokumentation der jeweiligen Datenbank. --- ### 6.4 CTEs haben keine eigenen Indizes Da CTEs keine physischen Tabellen sind, kannst du: - keine Indexe darauf erstellen - keine Statistiken direkt auf CTE-Ebene pflegen D. h.: - Der Optimizer muss die Abfrage so gut wie möglich auf Grundlage der zugrundeliegenden Tabellen optimieren. - Bei Performanceproblemen kann eine echte (temporäre) Tabelle mit Index manchmal besser sein. --- ## 7. Wann sollte man CTEs einsetzen? – Orientierung **Geeignet für CTEs:** - Wenn eine Abfrage **schwer lesbar** ist, weil sie viele Unterabfragen enthält. - Wenn du **dasselbe Zwischenergebnis mehrfach** in einer Abfrage benötigst. - Wenn du **schrittweise Transformationen** ausdrücken möchtest („Step 1, Step 2, …“). - Wenn du **Hierarchien** oder rekursive Strukturen mit SQL abbilden willst. **Eher nicht ideal:** - Für dauerhafte, von vielen Personen genutzte Logik → eher **Views** oder **Stored Procedures**. - Für sehr große Zwischenergebnisse, die oft wiederverwendet werden → evtl. **temporäre Tabellen** mit Index. - Wenn Performance schon kritisch ist → CTE-Einsatz testen und Ausführungspläne prüfen. --- ## Zusammenfassung - **Definition**: CTE = „Common Table Expression“ = eine **temporäre, benannte Ergebnismenge** innerhalb einer Abfrage, definiert mit `WITH`. - **Nutzen**: - Klarere Struktur und bessere Lesbarkeit komplexer SQL-Abfragen. - Wiederverwendbarkeit eines Zwischenergebnisses innerhalb derselben Abfrage. - Möglichkeit, komplexe Transformationen sequenziell auszudrücken. - Unterstützung rekursiver Abfragen (Hierarchien/Bäume). - **Abgrenzung**: - Gegenüber Subqueries: funktional ähnlich, aber meist lesbarer. - Gegenüber Views: CTEs sind **temporär**, Views **dauerhaft**. - Gegenüber Temp-Tabellen: CTEs sind logisch, Temp-Tabellen physisch und indexierbar. - **Herausforderungen**: - Performanceabhängig vom Datenbanksystem und vom Aufbau der Abfrage. - Übermäßiger Gebrauch kann Abfragen unübersichtlich machen. - Rekursive CTEs können komplex und teuer werden, brauchen sorgfältige Bedingungen.