Menu

SQLite Subqueries: SELECT in WHERE, FROM und SELECT

Wie du in SQLite ein SELECT in ein anderes verschachtelst – skalare Subqueries, IN/EXISTS, abgeleitete Tabellen, korrelierte Subqueries und wann ein JOIN besser passt.

Diese Seite enthält ausführbare Editoren — bearbeiten, ausführen und Ausgabe sofort sehen.

Eine Subquery ist ein SELECT innerhalb eines SELECT

Eine Subquery (oder Unterabfrage) ist genau das, wonach es klingt: ein SELECT-Statement, das in einem anderen Statement steckt und in Klammern eingefasst ist. SQLite führt zuerst die innere Abfrage aus, nimmt deren Ergebnis und reicht es an die äußere Abfrage weiter.

Bauen wir uns ein kleines Beispiel, das wir später mehrfach verwenden können:

Fünf Bestellungen, vier Kunden – zwei davon haben noch nie etwas bestellt. Mit diesem Datensatz arbeiten wir im Folgenden durchgehend.

SQLite Subquery in WHERE: Nach einer Liste filtern

Die häufigste Variante einer SQLite Unterabfrage: Man holt sich in der inneren Abfrage eine Liste von IDs und filtert die äußere Abfrage dann genau danach.

Die innere Abfrage liefert jede customer_id, die in orders vorkommt. Die äußere Abfrage behält nur die Kunden, deren id in dieser Liste steht. Cleo, Boris und Ada tauchen auf – Dmitri (keine Bestellungen) eben nicht.

IN (SELECT ...) ist das Standardmuster für die Frage „welche Zeilen aus A haben eine Entsprechung in B?". Lies es im Kopf als „dort, wo der Wert dieser Spalte einer der Werte ist, die die innere Abfrage zurückgibt".

NOT IN: Vorsicht bei NULL-Werten

Die umgekehrte Frage – „welche Kunden haben noch nichts bestellt?" – ist nur eine Zeile entfernt:

Das funktioniert hier. Aber NOT IN hat eine fiese Falle: Sobald die Unterabfrage auch nur ein einziges NULL zurückgibt, wird der gesamte NOT IN-Ausdruck zu NULL (also nicht zu TRUE) – und du bekommst null Zeilen zurück. Überraschend, und ohne jede Fehlermeldung.

Faustregel beim Einsatz von NOT IN auf einer Spalte, die NULL enthalten könnte:

Oder du nimmst gleich NOT EXISTS – das hat dieses Problem gar nicht erst. Dazu kommen wir gleich.

Skalare Subquery: eine Zeile, eine Spalte

Eine skalare Subquery liefert genau einen Wert zurück – eine Zeile, eine Spalte – und kann überall dort eingesetzt werden, wo ein Wert erwartet wird.

Die innere Abfrage SELECT MAX(total) FROM orders liefert 200 zurück. Die äußere Abfrage filtert dann nach Bestellungen, die genau diesem Wert entsprechen. Praktisch immer dann, wenn du gegen einen Aggregatwert vergleichen musst.

Du kannst eine skalare Subquery auch in der SELECT-Liste verwenden, um jeder Zeile einen berechneten Wert anzuhängen:

Für jede Zeile aus customers wird die innere Abfrage einmal ausgeführt, wobei customers.id jeweils eingesetzt wird. Genau das nennt man eine korrelierte Subquery — dazu gleich mehr. Für solche "eine Zahl pro Zeile"-Fälle ist ein LEFT JOIN mit GROUP BY meist performanter, aber die skalare Variante liest sich einfach wunderschön.

EXISTS: Prüfen, ob überhaupt etwas passt

EXISTS ist der ruhigere Verwandte von IN. Werte interessieren ihn nicht — er schaut nur, ob die Subquery irgendeine Zeile zurückgibt. Üblicherweise schreibt man deshalb SELECT 1 hinein, weil die Spalte selbst keine Rolle spielt.

Damit findest du Kunden, die mindestens eine Bestellung über 100 aufgegeben haben. Die innere Abfrage greift auf c.id aus der äußeren Abfrage zu – genau das macht sie zu einer korrelierten Subquery. SQLite bricht den Scan der inneren Tabelle ab, sobald ein Treffer gefunden wird. Deshalb ist EXISTS bei der Frage „Gibt es zu dieser Zeile einen passenden Datensatz?" oft schneller als IN.

Die Verneinung NOT EXISTS ist die NULL-sichere Variante, um nach „kein verknüpfter Datensatz vorhanden" zu fragen:

Subquery im FROM: Eine abgeleitete Tabelle

Eine Unterabfrage darf überall dort stehen, wo auch eine Tabelle erlaubt ist — auch in der FROM-Klausel. Die innere Abfrage wird dann zu einer temporären, benannten „abgeleiteten Tabelle" (derived table), die du joinen, filtern oder aggregieren kannst.

Die innere Abfrage ermittelt eine Summe pro Kunde. Die äußere Abfrage berechnet daraus den Durchschnitt pro Land. Solche zweistufigen Aggregationen sind genau der Anwendungsfall für abgeleitete Tabellen – immer dann, wenn ein einzelnes GROUP BY nicht ausreicht.

Der Alias AS per_customer ist Pflicht: Jede abgeleitete Tabelle braucht einen Namen.

Korrelierte Subqueries: Auswertung pro Zeile der äußeren Abfrage

Eine Subquery ist korreliert, sobald sie auf eine Spalte aus der äußeren Abfrage zugreift. SQLite muss die innere Abfrage dann für jede Zeile der äußeren Abfrage erneut ausführen – das ist sehr flexibel, kann aber schnell teuer werden.

Für jeden Kunden die größte Bestellung ermitteln. Die innere Abfrage hängt von customers.id ab und wird daher einmal pro Kunde ausgeführt. Kunden ohne Bestellungen bekommen NULL zurück – genau das, was man will.

Korrelierte Subqueries sind die natürliche Wahl für „nimm jede Zeile aus A und berechne etwas aus B". Bei kleinen Tabellen oder mit passendem Index läuft das problemlos. Bei großen Tabellen ohne unterstützenden Index lieber vorher messen, bevor du es in Produktion schickst – ein JOIN mit GROUP BY ist oft schneller.

SQLite Subquery oder JOIN: Was nehmen?

Die folgenden zwei Abfragen liefern dieselbe Antwort:

Beide Varianten liefern dieselben Zeilen. Der Optimizer von SQLite schreibt die eine Form intern oft sowieso in die andere um. Entscheide also nach Lesbarkeit:

  • Nimm eine Subquery, wenn du nur filtern willst und die Spalten der inneren Tabelle nichts im Endergebnis verloren haben.
  • Nimm einen JOIN, wenn das Ergebnis Spalten aus beiden Tabellen braucht.
  • Nimm EXISTS, wenn die eigentliche Frage lautet: „Gibt es mindestens eine passende Zeile?" — das liest sich klarer und du tappst nicht in die NULL-Fallen von IN/NOT IN.

Im Zweifel: Schreib die Variante, die sich beim Vorlesen von selbst erklärt.

Typische Falle: Subquery liefert mehrere Zeilen

Eine Subquery mit = darf höchstens eine Zeile zurückgeben. Kommen mehr zurück, schnappt sich SQLite einfach eine davon (quasi zufällig) — und du bekommst stillschweigend falsche Ergebnisse, ganz ohne Fehlermeldung.

Setze IN ein, wenn die innere Abfrage mehrere Zeilen zurückliefern kann:

Wenn du genau eine Zeile erwartest und das auch erzwingen willst, ergänze LIMIT 1 plus ein ORDER BY – so ist die Auswahl wenigstens deterministisch. Noch besser: Formuliere die Abfrage so, dass die einzelne Zeile bereits durch die Daten garantiert ist (etwa über einen Filter auf eine eindeutige Spalte).

Weiter geht's: Common Table Expressions

Unterabfragen im FROM werden schnell unübersichtlich – vor allem dann, wenn du dieselbe abgeleitete Tabelle zweimal brauchst oder die Verschachtelung drei Ebenen tief geht. Mit Common Table Expressions (WITH ... AS (...)) kannst du eine Subquery vorab benennen und sie im Rest des Statements einfach über ihren Namen ansprechen. Genau darum geht es auf der nächsten Seite.

Häufig gestellte Fragen

Was ist eine Subquery in SQLite?

Eine Subquery ist ein SELECT, das innerhalb eines anderen Statements steht und in Klammern eingeschlossen wird. SQLite führt erst die innere Abfrage aus und reicht das Ergebnis dann an die äußere weiter. Subqueries kannst du in WHERE, FROM, SELECT und einigen weiteren Klauseln benutzen.

Was ist der Unterschied zwischen IN und EXISTS in SQLite?

IN (SELECT ...) prüft, ob ein Wert mit irgendeiner Zeile übereinstimmt, die die Subquery liefert. EXISTS (SELECT ...) schaut dagegen nur, ob die Subquery überhaupt eine Zeile zurückgibt – die konkreten Werte sind egal. Sobald die innere Abfrage auf eine Spalte der äußeren zugreift (also korreliert ist), ist EXISTS meistens die bessere Wahl.

Subquery oder JOIN – was nehme ich in SQLite?

Ein JOIN ist richtig, wenn du Spalten aus beiden Tabellen im Ergebnis brauchst. Eine Subquery reicht, wenn du nur filtern willst oder einen einzelnen Wert berechnest. Der Optimizer von SQLite formt die beiden Varianten ohnehin oft ineinander um – nimm also das, was sich beim Lesen klarer anfühlt.

Was ist eine korrelierte Subquery in SQLite?

Eine korrelierte Subquery greift auf eine Spalte der äußeren Abfrage zu und muss deshalb für jede Zeile der äußeren Abfrage neu ausgewertet werden. Das ist flexibel, kann bei großen Tabellen aber richtig bremsen. Wird so eine Subquery zum Flaschenhals, hilft oft das Umschreiben als JOIN oder als CTE.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S