Alle BeiträgeDas PerSight-Team · Zuletzt aktualisiert: 23. Juli 2026 · 8 Min. Lesezeit

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'
);
SQL Server kennt das DATE-Literal nicht; schreiben Sie das Datum dort als einfachen String (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
);
Robust gegenüber NULL; dies statt NOT IN bevorzugen.

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