- 1 Section
- 10 Lessons
- unbegrenzt
- SQL Fortgeschritten10
- 1.1Alle JOIN-Typen: RIGHT, FULL OUTER, CROSS, SELF
- 1.2Unterabfragen (Subqueries)
- 1.3Common Table Expressions (CTEs)
- 1.4Window Functions: ROW_NUMBER, RANK, PARTITION BY
- 1.5Views erstellen und nutzen
- 1.6Stored Procedures und Funktionen
- 1.7Trigger
- 1.8Datenbankindizes und Query-Optimierung
- 1.9Datenbankreporte und komplexe Auswertungen
- 1.10SQL-Fortgeschrittenenaufgaben
Unterabfragen (Subqueries)
Eine Unterabfrage (englisch subquery) ist ein SELECT-Statement, das innerhalb eines anderen SQL-Statements steht. Du baust also Abfragen ineinander, statt sie nebeneinander zu schreiben. Subqueries sind eines der mächtigsten Werkzeuge in SQL und tauchen in IHK-Klausuren immer wieder auf.
In dieser Lektion lernst du die vier Subquery-Typen kennen (Scalar, Row, Column, Table), wo Subqueries stehen können (WHERE, FROM, SELECT), den Unterschied zwischen korrelierten und nicht-korrelierten Subqueries und wann du sie gegen einen JOIN tauschen solltest.
1) Was ist eine Subquery?
Eine Subquery ist eine geschachtelte Abfrage – ein SELECT in Klammern, dessen Ergebnis von der äußeren Abfrage weiterverwendet wird:
SELECT name FROM kunden WHERE id IN (SELECT kunde_id FROM bestellungen);
Die innere Abfrage SELECT kunde_id FROM bestellungen liefert eine Liste von IDs. Die äußere Abfrage gibt nur die Kunden zurück, deren id in dieser Liste enthalten ist – also alle Kunden, die mindestens eine Bestellung haben.
Wichtig zu verstehen: die Datenbank wertet die innere Abfrage zuerst aus (vereinfacht gedacht – moderne Optimizer machen das oft anders), das Ergebnis wird dann in die äußere eingesetzt. Das nennt man nicht-korrelierte Subquery.
2) Beispiel-Daten für diese Lektion
Wir nutzen drei Tabellen: kunden, bestellungen und artikel. Klein gehalten, damit die Ergebnisse nachvollziehbar bleiben:
| id | name | ort |
|---|---|---|
| 1 | Anna | Berlin |
| 2 | Ben | München |
| 3 | Carla | Berlin |
| 4 | Dirk | Köln |
| id | kunde_id | summe |
|---|---|---|
| 10 | 1 | 500 |
| 11 | 1 | 200 |
| 12 | 2 | 1200 |
| 13 | 3 | 50 |
| id | preis |
|---|---|
| 1 | 100 |
| 2 | 250 |
| 3 | 50 |
| 4 | 800 |
3) Die vier Subquery-Typen nach Ergebnis
Subqueries unterscheiden sich danach, was sie zurückliefern. Daraus ergibt sich, wo sie verwendet werden dürfen:
=, >, in SELECT).WHERE (a, b) = (SELECT …).IN, ANY, ALL.FROM – wie eine eingebettete Tabelle.=), plötzlich mehrere Zeilen zurückgibt, bricht die Abfrage mit einem Fehler ab: „Subquery returned more than 1 row". Klassische Klausur-Falle.4) Skalare Subquery in WHERE
Die häufigste Form: eine Subquery liefert einen einzelnen Wert, der mit =, <, > usw. verglichen wird. Beispiel: „Welche Bestellung hat eine höhere Summe als der Durchschnitt?"
SELECT id, summe FROM bestellungen WHERE summe > (SELECT AVG(summe) FROM bestellungen);
| id | summe |
|---|---|
| 10 | 500 |
| 12 | 1200 |
5) Spalten-Subquery mit IN
Wenn die Subquery mehrere Werte liefert, brauchst du IN, ANY oder ALL:
-- Alle Kunden mit mindestens einer Bestellung SELECT name FROM kunden WHERE id IN (SELECT kunde_id FROM bestellungen); -- Alle Kunden OHNE Bestellung SELECT name FROM kunden WHERE id NOT IN (SELECT kunde_id FROM bestellungen);
NULL-Wert zurückgibt, liefert NOT IN kein einziges Ergebnis – egal wie viele Werte sonst noch dabei sind. Grund: x NOT IN (1, NULL) wird zu x ≠ 1 AND x ≠ NULL, und der zweite Vergleich ist UNKNOWN. Sicherer ist NOT EXISTS oder ein zusätzliches WHERE … IS NOT NULL.
6) ANY und ALL
ANY (Synonym SOME) und ALL erlauben Vergleichsoperatoren mit Mehrwert-Subqueries:
-- Bestellungen größer als IRGENDEINE Bestellung aus Berlin-Kunden SELECT id, summe FROM bestellungen WHERE summe > ANY (SELECT summe FROM bestellungen b JOIN kunden k ON b.kunde_id = k.id WHERE k.ort = 'Berlin'); -- Bestellungen größer als ALLE Bestellungen aus Berlin-Kunden SELECT id, summe FROM bestellungen WHERE summe > ALL (SELECT summe FROM bestellungen b JOIN kunden k ON b.kunde_id = k.id WHERE k.ort = 'Berlin');
Eselsbrücke: > ANY bedeutet „größer als das Minimum der Liste" (es reicht ein Treffer). > ALL bedeutet „größer als das Maximum der Liste" (muss alle überbieten). In der Praxis sieht man ANY/ALL selten – meist nutzt man stattdessen MAX()/MIN() in der Subquery, was lesbarer ist.
7) EXISTS und NOT EXISTS
EXISTS prüft nur, ob die Subquery überhaupt Zeilen zurückgibt – egal welche. Die Spalten der Subquery sind irrelevant, deshalb schreibt man oft SELECT 1 oder SELECT *:
-- Alle Kunden, die mindestens eine Bestellung haben SELECT name FROM kunden k WHERE EXISTS (SELECT 1 FROM bestellungen b WHERE b.kunde_id = k.id);
Das ist eine korrelierte Subquery – mehr dazu im nächsten Abschnitt. EXISTS ist oft schneller als IN, weil die Datenbank nach dem ersten Treffer abbrechen kann.
8) Korrelierte vs. nicht-korrelierte Subqueries
Der entscheidende Unterschied:
- Nicht-korreliert: die innere Abfrage kann eigenständig ausgeführt werden. Ergebnis wird einmal berechnet und in der äußeren verwendet.
- Korreliert: die innere Abfrage referenziert eine Spalte der äußeren. Sie muss für jede Zeile der äußeren neu ausgewertet werden.
SELECT name FROM kunden WHERE id IN (SELECT kunde_id FROM bestellungen);
k.id der äußeren):SELECT name FROM kunden k WHERE EXISTS (SELECT 1 FROM bestellungen b WHERE b.kunde_id = k.id);
k.id der äußeren ab. Logisch wird sie für jede Kundenzeile einmal ausgeführt – in der Praxis optimiert die Datenbank das oft anders, aber das mentale Modell hilft beim Verständnis.9) Subquery in der SELECT-Liste
Eine skalare Subquery kann auch in der SELECT-Liste stehen – pro Zeile wird ein einzelner Wert berechnet:
SELECT k.name, (SELECT COUNT(*) FROM bestellungen b WHERE b.kunde_id = k.id) AS anzahl_bestellungen FROM kunden k;
| name | anzahl_bestellungen |
|---|---|
| Anna | 2 |
| Ben | 1 |
| Carla | 1 |
| Dirk | 0 |
LEFT JOIN + GROUP BY – beide Varianten sind möglich. Die Subquery-Variante ist oft lesbarer, der JOIN bei vielen Zeilen aber meist schneller.10) Subquery in FROM (abgeleitete Tabelle)
Im FROM verwendete Subqueries nennt man Inline-Views oder derived tables. Sie verhalten sich wie temporäre Tabellen und brauchen einen Alias:
SELECT ort, AVG(summe_pro_kunde) AS avg_kundenwert FROM (SELECT k.ort, k.id, SUM(b.summe) AS summe_pro_kunde FROM kunden k LEFT JOIN bestellungen b ON k.id = b.kunde_id GROUP BY k.ort, k.id) AS t GROUP BY ort;
Hier berechnet die innere Abfrage zuerst die Summe pro Kunde, die äußere bildet den Durchschnitt pro Ort. Solche zweistufigen Aggregationen sind in CTEs (L3) oft sauberer zu lesen, funktional aber äquivalent.
11) Subquery in INSERT, UPDATE, DELETE
Subqueries gibt es nicht nur in SELECT. Sie funktionieren auch in DML-Statements:
-- Alle Bestellungen aus München um 10 % rabattieren UPDATE bestellungen SET summe = summe * 0.9 WHERE kunde_id IN (SELECT id FROM kunden WHERE ort = 'München'); -- Alle Kunden ohne Bestellungen löschen DELETE FROM kunden WHERE id NOT IN (SELECT kunde_id FROM bestellungen WHERE kunde_id IS NOT NULL); -- Daten aus einer Quelltabelle in eine Zieltabelle kopieren INSERT INTO bestellungen_archiv (id, kunde_id, summe) SELECT id, kunde_id, summe FROM bestellungen WHERE bestelldatum < '2024-01-01';
Bei INSERT … SELECT kannst du Daten gleich tabellenweise umfüllen – sehr nützlich für Datenmigration oder Archivierung.
12) Subquery vs. JOIN – wann was?
Vieles geht auf beide Arten. Faustregeln:
- Brauchst du Spalten aus beiden Tabellen im Ergebnis → JOIN
- Willst du nur filtern (eine Tabelle), ohne Spalten der zweiten anzuzeigen → Subquery (
WHERE … INoderEXISTS) - Brauchst du eine Aggregation, die als einzelner Vergleichswert dient (Durchschnitt, Max) → Skalare Subquery
- Steht eine Zwischenrechnung im Vordergrund, die du wiederverwenden willst → CTE
Moderne Optimizer wandeln viele Subqueries intern in JOINs um. Trotzdem sind beide Formen wichtig, weil sie unterschiedlich lesbar sind. Lesbarkeit > Mikrooptimierung, solange die Abfrage schnell genug ist.
13) Häufige Fehler und Fallstricke
- „Subquery returned more than 1 row": skalare Subquery liefert plötzlich mehrere Zeilen. Lösung:
LIMIT 1, oder Aggregat (MAX,MIN), oder Wechsel aufIN. - NULL bei NOT IN: siehe oben – leeres Ergebnis ohne erkennbaren Grund.
NOT EXISTSist robuster. - Performance bei korrelierten Subqueries: pro Zeile der äußeren Abfrage wird die innere ausgeführt. Bei großen Tabellen kann das langsam werden – Index auf der referenzierten Spalte hilft.
- Vergessenes Alias bei FROM-Subqueries:
SELECT … FROM (SELECT …)ohne Alias führt zum Syntaxfehler. - Subquery ohne ORDER BY in TOP-N-Suchen:
(SELECT … LIMIT 1)ohneORDER BYgibt irgendeine Zeile zurück, nicht zuverlässig dieselbe.
14) Wann lieber CTE statt Subquery?
Wenn deine Subquery länger als ein paar Zeilen wird, mehrfach verwendet wird oder die Abfrage tief verschachtelt ist, ist eine Common Table Expression (CTE) meist die bessere Wahl. CTEs geben dem Zwischenresultat einen Namen und stehen vor dem eigentlichen SELECT – die Abfrage liest sich top-down statt verschachtelt.
(SELECT AVG …) sind dafür der direkte Weg. Wer Subqueries beherrscht, löst diese Aufgaben fast immer einfacher als mit verschachtelten JOINs.
Zusammenfassung
Eine Subquery ist ein SELECT, das in einem anderen SQL-Statement steht. Vier Typen nach Ergebnisform: skalar (1×1), Zeile, Spalte und Tabelle. Sie können in WHERE (mit =, IN, EXISTS, ANY, ALL), in FROM (Inline-View) und in SELECT (skalare Werte pro Zeile) stehen. Wichtig zu unterscheiden sind korrelierte Subqueries (verweisen auf die äußere Abfrage, werden pro Zeile ausgewertet) und nicht-korrelierte (eigenständig auswertbar). Bei NOT IN auf NULL-Werte achten – sicherer ist meist NOT EXISTS.
