init at work

This commit is contained in:
Mathias Schneider
2026-03-27 12:58:01 +01:00
parent 352c352056
commit 8d40597732
60 changed files with 16498 additions and 0 deletions
+549
View File
@@ -0,0 +1,549 @@
#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.