In SQL-Tutorials wirken Subqueries (verschachtelte Abfragen) oft wie ein Fremdkörper: plötzlich zusätzliche SELECTs in Klammern, schwer zu lesen und noch schwerer zu debuggen. Gleichzeitig lösen sie im Alltag viele Probleme elegant, wenn sie bewusst eingesetzt werden.
Dieser Artikel zeigt Schritt für Schritt, wie Subqueries funktionieren, welche Typen es gibt, wie sie sich von JOINs unterscheiden und worauf bei Performance und Lesbarkeit zu achten ist.
SQL Subquery Grundlagen – was verschachtelte Abfragen leisten
Eine Subquery ist eine SQL-Abfrage, die in einer anderen Abfrage eingebettet ist. Typisch steht sie in Klammern und liefert ein Zwischenergebnis, das die äußere Abfrage weiterverarbeitet.
Ein einfaches Beispiel: Alle Bestellungen des umsatzstärksten Kunden holen.
Statt in mehreren Schritten zu arbeiten (erst Kunde ermitteln, dann Bestellungen holen), kann eine Subquery das direkt lösen:
SELECT *
FROM orders
WHERE customer_id = (
SELECT customer_id
FROM customers
ORDER BY total_revenue DESC
LIMIT 1
);
Wichtige Eigenschaften von Subqueries:
- Sie stehen in Klammern.
- Sie können Werte (Skalar), eine Liste oder ganze Tabellen liefern.
- Sie dürfen in SELECT, FROM, WHERE und HAVING (und je nach Datenbank auch in anderen Bereichen) verwendet werden.
Verschiedene Ergebnisarten von Subqueries
Subqueries lassen sich grob nach ihrem Ergebnis einteilen:
- Skalare Subquery: liefert genau einen Wert (eine Zeile, eine Spalte).
- Zeilen- oder Tabellensubquery: liefert mehrere Spalten und Zeilen.
- Listensubquery: liefert eine Spalte mit mehreren Zeilen (z. B. für IN).
Diese Unterscheidung ist wichtig, weil viele Fehler daher kommen, dass eine Subquery mehr oder weniger liefert, als der äußere Ausdruck erwartet.
Subqueries im WHERE – typische Anwendungsfälle
Am häufigsten tauchen Subqueries im WHERE-Teil auf. Sie dienen als Filter, der dynamisch von einem anderen SELECT abhängt.
Beispiel: IN, NOT IN, EXISTS richtig einsetzen
Ein klassischer Use-Case: Alle Kunden mit mindestens einer Bestellung finden.
SELECT *
FROM customers c
WHERE c.id IN (
SELECT DISTINCT customer_id
FROM orders
);
Die innere Abfrage liefert alle Kunden-IDs, die in der Bestelltabelle vorkommen. Die äußere Abfrage filtert entsprechend.
Das Gleiche mit EXISTS kann performanter und oft lesbarer sein:
SELECT *
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
);
Hier prüft die Datenbank: „Gibt es mindestens eine passende Zeile in orders für diesen Kunden?“ – Sobald das der Fall ist, muss nicht weiter gesucht werden.
Skalare Subqueries in WHERE
Skalare Subqueries vergleichen einen einzelnen Wert, zum Beispiel mit einem Aggregat. Alle Bestellungen, die über dem durchschnittlichen Bestellwert liegen:
SELECT *
FROM orders
WHERE amount > (
SELECT AVG(amount)
FROM orders
);
Die innere Abfrage liefert genau eine Zahl (den Durchschnitt), die äußere Abfrage nutzt sie im Vergleich.
Subqueries im FROM – abgeleitete Tabellen nutzen
Subqueries im FROM-Teil bilden eine abgeleitete Tabelle (auch Derived Table oder Inline View genannt). Damit lassen sich Zwischenergebnisse klar strukturieren.
Abgeleitete Tabellen mit Aggregaten
Angenommen, der monatliche Umsatz pro Kunde soll berechnet und anschließend gefiltert werden. Statt eine sehr komplexe Abfrage zu schreiben, lässt sich der Aggregat-Teil in eine Subquery auslagern:
SELECT t.customer_id, t.month, t.revenue
FROM (
SELECT customer_id,
DATE_TRUNC(‚month‘, order_date) AS month,
SUM(amount) AS revenue
FROM orders
GROUP BY customer_id, DATE_TRUNC(‚month‘, order_date)
) AS t
WHERE t.revenue > 1000;
Vorteile dieser Struktur:
- Der Aggregat-Block steht für sich und ist leichter zu verstehen.
- Im äußeren SELECT kann nach Belieben weiter gefiltert oder gejoint werden.
- Die Abfrage bleibt flexibler, wenn neue Bedingungen hinzukommen.
Subquery vs. View
Viele Datenbanken erlauben, häufig genutzte Subqueries als View zu speichern. Der Unterschied:
- Subquery: nur in dieser einen Abfrage sichtbar.
- View: als „virtuelle Tabelle“ wiederverwendbar.
Bei einmaligen, eher spezifischen Auswertungen ist die Subquery im FROM oft die pragmatischste Lösung.
Subqueries im SELECT – berechnete Werte pro Zeile
Subqueries im SELECT-Teil liefern zusätzliche Spalten, die sich aus anderen Tabellen oder Aggregaten ableiten. Sie können praktisch sein, machen Abfragen aber schnell unübersichtlich und manchmal langsam.
Beispiel: Summen oder Counts pro Zeile
Alle Kunden mit der Summe ihrer Bestellungen anzeigen, ohne GROUP BY zu verwenden:
SELECT c.id,
c.name,
(
SELECT COALESCE(SUM(amount), 0)
FROM orders o
WHERE o.customer_id = c.id
) AS total_amount
FROM customers c;
Diese Subquery wird pro Kunde ausgeführt. Das ist gut lesbar, kann aber bei vielen Zeilen teuer werden. Alternativ lässt sich oft ein JOIN mit Aggregation nutzen, wie in vielen Beispielen zu SQL JOINs gezeigt wird.
Wann SELECT-Subqueries sinnvoll sind
SELECT-Subqueries lohnen sich vor allem, wenn:
- nur wenige Zeilen verarbeitet werden,
- die Logik pro Zeile klar und einfach bleibt,
- ein JOIN den Code deutlich komplizierter machen würde.
Sobald mehrere solcher Subqueries pro Zeile nötig werden, ist ein Umstieg auf Gruppenabfragen mit JOINs meist die bessere Wahl.
Korrelierte Subqueries – wenn die innere Abfrage die äußere braucht
Eine korrelierte Subquery greift auf Spalten der äußeren Abfrage zu. Sie wird quasi für jede Zeile der äußeren Abfrage neu ausgewertet.
Beispiel für eine korrelierte Subquery
Für jede Bestellung soll angezeigt werden, ob sie über dem durchschnittlichen Bestellwert des jeweiligen Kunden liegt:
SELECT o.id,
o.customer_id,
o.amount,
CASE WHEN o.amount > (
SELECT AVG(o2.amount)
FROM orders o2
WHERE o2.customer_id = o.customer_id
) THEN ‚above_avg‘ ELSE ‚below_or_equal‘ END AS amount_flag
FROM orders o;
Die innere Abfrage bezieht sich auf o.customer_id aus der äußeren Abfrage. Das ist der Kern einer korrelierten Subquery.
Leistungsaspekte korrelierter Subqueries
Korrelierte Subqueries können leicht langsam werden, weil sie vom Query-Planner nicht immer gut optimiert werden. Oft sind diese Varianten effizienter:
- Vorab eine Aggregation nach Kunden berechnen (z. B. in einer Subquery im FROM) und dann joinen.
- Window-Funktionen nutzen, falls die Datenbank sie unterstützt, ähnlich wie bei SQL Window Functions.
Faustregel: Korrelierte Subqueries sparsam und bewusst einsetzen, vor allem bei großen Tabellen.
Subquery oder JOIN – was wann sinnvoll ist?
Viele Abfragen lassen sich sowohl mit JOINs als auch mit Subqueries schreiben. Die Wahl hat Auswirkungen auf Lesbarkeit und Performance.
Subquery vs. JOIN im Vergleich
| Aspekt | Subquery | JOIN |
|---|---|---|
| Lesbarkeit | Gut, wenn Zwischenlogik gekapselt wird. | Gut, wenn Beziehungen zwischen Tabellen im Vordergrund stehen. |
| Performance | Kann langsamer sein, vor allem bei korrelierten Varianten. | Oft schneller, besonders mit passenden Indexen. |
| Wiederverwendbarkeit | Begrenzt; eignet sich für spezifische Abfragen. | Gut; Muster lassen sich auf andere Abfragen übertragen. |
| Komplexe Filterlogik | Stark im WHERE/HAVING-Bereich. | Stark, wenn mehrere Tabellen kombiniert werden müssen. |
Entscheidungshilfe: Subquery oder JOIN?
- Wenn hauptsächlich Beziehungen zwischen Tabellen modelliert werden sollen → eher JOIN.
- Wenn ein komplexer Filter oder ein Aggregat als Vorbedingung nötig ist → Subquery im WHERE oder FROM in Betracht ziehen.
- Wenn Performance kritisch ist → JOIN-Variante bauen und mit der Subquery-Variante vergleichen.
- Wenn der Code schwer lesbar wird → Variante wählen, die die Logik klarer ausdrückt, notfalls mit Kommentaren.
Weitere Entscheidungskriterien rund um Datenbank-Design und -Abfragen finden sich auch im Artikel SQL Abfragen optimieren.
Typische Fehler mit Subqueries vermeiden
Bei verschachtelten Abfragen treten häufig wiederkehrende Stolpersteine auf.
Mehrere Zeilen, wo nur eine erwartet ist
Skalare Subqueries müssen genau eine Zeile liefern. Wenn die Subquery mehrere Zeilen zurückgibt, melden viele Datenbanken einen Fehler oder verhalten sich unerwartet.
Statt:
WHERE amount > (SELECT amount FROM orders)
besser:
WHERE amount > (
SELECT AVG(amount)
FROM orders
)
oder (je nach Anforderung) eine passende Aggregatfunktion wählen. Wo mehrere Zeilen erwartet werden, passen Operatoren wie IN, ANY, SOME oder ALL besser.
NULL-Fallen bei IN / NOT IN
Ein weiterer Klassiker: NOT IN mit Subqueries, die NULL liefern. Sobald NULL in der Ergebnisliste steht, kann die gesamte Bedingung „UNKNOWN“ werden und Zeilen fallen unerwartet heraus.
Robustere Varianten sind:
- Subquery so formulieren, dass keine NULLs entstehen (z. B. WHERE customer_id IS NOT NULL).
- NOT EXISTS statt NOT IN verwenden.
Unnötig verschachtelte Logik
Subqueries verleiten dazu, jede Idee direkt in Klammern zu gießen. Oft ist eine klar strukturierte Abfrage mit JOINs und gutem Alias-Namen besser lesbar und passt zu Clean-Code-Prinzipien, wie sie auch für Clean Code im Frontend gelten.
Checkliste für saubere SQL Subqueries im Alltag
Zum Abschluss eine kompakte Checkliste, die bei der Planung und beim Review von Abfragen mit Subqueries hilft.
Subquery-Check – kompakte Praxis-Checkliste
- Klarheit: Ist die Rolle der Subquery in einem Satz erklärbar (z. B. „liefert alle aktiven Kunden“)?
- Ergebnisart: Liefert sie genau das, was der Kontext braucht (ein Wert, eine Liste, eine Tabelle)?
- Korrelationsbedarf: Muss die Subquery wirklich auf die äußere Zeile zugreifen – oder reicht eine normale Abfrage im FROM mit JOIN?
- Performance: Wird die Subquery je Zeile ausgeführt? Wenn ja, sind Indexe vorhanden und ist die Datenmenge vertretbar?
- Lesbarkeit: Wäre die Abfrage mit einem JOIN in einer Zeile leichter verständlich?
- NULL-Verhalten: Können NULL-Werte in der Subquery Probleme mit IN/NOT IN verursachen?
- Wiederverwendung: Sollte die Logik eher als View oder Materialized View abgebildet werden?
Wer diese Fragen konsequent durchgeht, nutzt Subqueries als Werkzeug – und nicht als versteckten Problemverursacher. In Kombination mit gut geplanten JOINs und Indexen werden SQL-Abfragen robuster, nachvollziehbarer und besser wartbar.

