SQL-Unterabfragen: Wann, wie und die NOT-IN-Falle
Eine Abfrage in der Abfrage: skalare Unterabfragen, IN, EXISTS und korrelierte Unterabfragen. Warum NOT IN bei NULL-Werten nichts zurückgibt und wann eine Unterabfrage einem JOIN vorzuziehen ist.
Eine Unterabfrage ist, wie der Name sagt, eine Abfrage, die innerhalb einer anderen Abfrage läuft. Sie zahlt sich überall dort aus, wo Sie erst ein Zwischenergebnis berechnen und dann danach filtern müssen, etwa "Produkte über dem Durchschnittspreis" oder "Kunden, die nie bestellt haben". Es gibt einige Arten und eine berühmte Falle; sehen wir sie uns an.
Die skalare Unterabfrage: eine Abfrage, die einen Wert liefert
Die einfachste Art liefert einen einzelnen Wert (eine Zeile, eine Spalte), den Sie in einem Vergleich verwenden. Produkte über dem Durchschnittspreis:
SELECT name, preis
FROM produkte
WHERE preis > (SELECT AVG(preis) FROM produkte);Die Abfrage in Klammern läuft zuerst und ermittelt den Durchschnitt; die äußere Abfrage vergleicht jedes Produkt damit. Eine skalare Unterabfrage kann auch in der SELECT-Liste stehen, etwa um den Gesamtdurchschnitt neben jedes Produkt zu schreiben.
IN und EXISTS: "steht es in dieser Liste?"
Eine Frage wie "Kunden, die dieses Jahr bestellt haben" dreht sich eigentlich um die Mitgliedschaft in einer Liste. Es gibt zwei gängige Wege: IN vergleicht mit einer Werteliste, EXISTS mit dem Vorhandensein einer Zeile:
-- Mit IN
SELECT name
FROM kunden
WHERE id IN (
SELECT kunden_id
FROM bestellungen
WHERE bestelldatum >= DATE '2026-01-01'
);
-- Mit EXISTS (oft effizienter)
SELECT k.name
FROM kunden k
WHERE EXISTS (
SELECT 1
FROM bestellungen b
WHERE b.kunden_id = k.id
AND b.bestelldatum >= DATE '2026-01-01'
);EXISTS hält an, sobald es die erste Übereinstimmung findet, und ist auf großen Tabellen meist schneller. IN liest sich tendenziell leichter. Bei kleinen Listen ist der Unterschied vernachlässigbar; bei großen Datenmengen bevorzugen Sie EXISTS.
Achtung: die NOT-IN-und-NULL-Falle
Diese erwischt selbst Erfahrene. Enthält die von der Unterabfrage gelieferte Liste auch nur ein einziges NULL, gibt NOT IN gar keine Zeilen zurück, ohne Fehler, einfach ein stillschweigend leeres Ergebnis. Der Grund: Ein Vergleich mit NULL ergibt "unbekannt". Um "Kunden, die nie bestellt haben" ohne diese Falle zu finden, verwenden Sie NOT EXISTS:
SELECT k.name
FROM kunden k
WHERE NOT EXISTS (
SELECT 1
FROM bestellungen b
WHERE b.kunden_id = k.id
);Was ist eine korrelierte Unterabfrage?
In den EXISTS-Beispielen oben verweist die Unterabfrage auf die äußere Zeile (k.id). Das ist eine korrelierte Unterabfrage: Sie läuft einmal pro äußerer Zeile. Sie ist mächtig, kann aber bei unbedachter Nutzung langsam werden; oft erhalten Sie dasselbe Ergebnis schneller mit einem JOIN oder einer Fensterfunktion.
Häufig gestellte Fragen
- Was ist schneller, eine Unterabfrage oder ein JOIN?
- Moderne Datenbanken optimieren die meisten Unterabfragen intern zu etwas JOIN-Ähnlichem, daher ist der Unterschied meist klein. Wählen Sie nach Lesbarkeit: einen JOIN, wenn Sie Spalten der anderen Tabelle brauchen, IN/EXISTS, wenn Sie nur nach Mitgliedschaft/Vorhandensein filtern. Im Zweifel schreiben Sie beides und sehen sich den Abfrageplan an.
- Warum gibt NOT IN manchmal nichts zurück?
- Enthält die von der Unterabfrage gelieferte Liste ein NULL, kippt die Logik von NOT IN auf "unbekannt" und keine Zeile kommt durch. Die Lösung: NOT EXISTS verwenden oder NULL in der Unterabfrage ausfiltern (WHERE spalte IS NOT NULL).
- Kann ich eine Unterabfrage im FROM verwenden (abgeleitete Tabelle)?
- Ja. Mit FROM (SELECT ...) t nutzen Sie eine Unterabfrage wie eine temporäre Tabelle; genau das tut das Top-N-pro-Gruppe-Muster. Für die Lesbarkeit können Sie dasselbe als CTE (WITH ...) schreiben.
- Was, wenn die Unterabfrage mehr als eine Zeile liefert?
- Wo ein Skalar erwartet wird (etwa mit =), löst eine Unterabfrage mit mehreren Zeilen einen Fehler aus. In solchen Fällen verwenden Sie IN oder reduzieren die Unterabfrage auf eine Zeile (etwa mit MAX oder LIMIT 1).
Wenn Sie nicht zwischen IN und EXISTS abwägen möchten, überlassen Sie die Frage PerSight. Geben Sie "zeige Kunden, die nie bestellt haben" ein, und es wählt das passende Muster und erklärt in natürlicher Sprache das erzeugte SQL.
Quellen
Verwandte Artikel
- SQL JOIN: Zwei Tabellen richtig verbindenEin praktischer Leitfaden zum Verbinden von Tabellen wie Bestellungen und Kunden in SQL: der Unterschied zwischen INNER JOIN und LEFT JOIN, die Falle der vervielfachten Zeilen und Beispiele, die auf allen vier Datenbanken laufen.
- Die Top-N-Zeilen pro Gruppe in SQLDie drei teuersten Produkte je Kategorie, die letzte Bestellung jedes Kunden: die Top-N-Zeilen pro Gruppe mit ROW_NUMBER finden. Das Fensterfunktions-Muster und ein Fallback für MySQL 5.7.
- Natürliche Sprache zu SQL: So funktioniert es und warum die sichtbare Abfrage zähltEin verständlicher Leitfaden zu natürlicher Sprache zu SQL: wie aus einer Frage eine Abfrage wird, warum eine schreibgeschützte, stets sichtbare Abfrage sicherer ist und wie PerSight Ihre Zeilendaten auf Ihrem Rechner hält.