Files
it-know-how/glossar/CTEs.md
T
2026-03-27 12:58:01 +01:00

549 lines
15 KiB
Markdown
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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.