Sehen, was gerade passiert ist
Wenn du ein INSERT, UPDATE oder DELETE ausführst, verrät dir SQLite zwar, wie viele Zeilen betroffen waren – aber nicht, welche Zeilen es waren oder wie deren finale Werte aussehen. Der klassische Umweg: hinterher noch ein SELECT absetzen. Das sind dann zwei Roundtrips, zwei Statements und obendrein ein kleines Zeitfenster, in dem jemand anderes die Zeile zwischendurch ändern könnte.
Genau hier kommt die RETURNING-Klausel ins Spiel. Du hängst sie an dein Schreib-Statement an, listest die gewünschten Spalten auf, und SQLite liefert dir die betroffenen Zeilen zurück – ganz so, als hättest du direkt ein SELECT darauf laufen lassen:
Eine Anweisung, ein Roundtrip — und du bekommst die generierte id sowie den Default-Wert von created_at zurück, den die Datenbank für dich gesetzt hat.
RETURNING kam mit SQLite 3.35.0 (März 2021) dazu. Wenn dein Statement mit einem Syntaxfehler abgelehnt wird, wirf einen Blick auf SELECT sqlite_version(); — ältere Versionen kennen das Keyword schlicht nicht.
Die generierte ID per INSERT RETURNING zurückbekommen
Der häufigste Grund, zur RETURNING-Klausel zu greifen, ist das Abholen des automatisch generierten Primärschlüssels direkt nach dem Insert:
Vor RETURNING musste man zuerst einfügen und dann auf derselben Verbindung last_insert_rowid() (bzw. das Pendant des jeweiligen Treibers) aufrufen. Das funktioniert nach wie vor, ist aber abhängig vom internen Zustand der Verbindung — und gerade bei Connection Pools oder mehreren Threads schnell eine Fehlerquelle. RETURNING id ist dagegen explizit, gehört direkt zum Statement und verhält sich gleich, egal wer die Verbindung verwaltet.
Falls deine Tabelle keinen expliziten INTEGER PRIMARY KEY deklariert, kommst du trotzdem an den impliziten Zeilenbezeichner heran:
Jede gewöhnliche SQLite-Tabelle hat eine rowid, und mit RETURNING bekommst du sie direkt zurückgeliefert.
Mehrere Spalten und Ausdrücke
Die RETURNING-Klausel akzeptiert dasselbe Format wie die Spaltenliste eines SELECT. Du kannst also Spalten auflisten, * verwenden, Ausdrücke bauen und ihnen Aliase vergeben:
Mit RETURNING * bekommst du alles zurück – inklusive der Default-Werte, die SQLite selbst eingesetzt hat – ohne dass du jede Spalte einzeln auflisten musst:
Du siehst die neue id, den von dir übergebenen name und den Zeitstempel, den SQLite berechnet hat.
RETURNING bei UPDATE
Bei einem UPDATE liefert dir die RETURNING-Klausel die Werte nach dem Update zurück – also die Zeile so, wie sie nach deinen Änderungen aussieht:
Du bekommst den neuen Kontostand von Ada zurück, also 125, und nicht den alten Wert 100. Genau deshalb ist RETURNING ideal für atomare Zähler oder Buchungen (Soll/Haben) — du musst nicht erst lesen, rechnen, schreiben und dann nochmal lesen.
Trifft das WHERE mehrere Zeilen, bekommst du pro betroffener Zeile einen Datensatz zurück:
Drei Zeilen rein, drei Zeilen raus. Auf die Reihenfolge solltest du dich nicht verlassen – wenn du eine bestimmte Sortierung brauchst, sortier das Ergebnis im Client.
sqlite delete returning: Zeilen vor dem Löschen abgreifen
Bei einem DELETE liefert dir RETURNING die Zeilen so, wie sie unmittelbar vor dem Löschen waren. Praktisch, um Daten zu archivieren, Audit-Trails zu pflegen oder einfach nachzusehen, was tatsächlich verschwunden ist:
Du bekommst die beiden abgelaufenen Sessions samt allen Feldern zurück, obwohl sie nicht mehr in der Tabelle stehen. Wenn du sie anderswo unterbringen willst, ist das die ideale Vorlage für eine Archivtabelle: Du liest das Ergebnis und schreibst es in derselben Transaktion in eine andere Tabelle.
RETURNING beim UPSERT in SQLite
Die RETURNING-Klausel funktioniert auch mit INSERT ... ON CONFLICT ... DO UPDATE. Die zurückgelieferte Zeile zeigt dabei genau das an, was tatsächlich passiert ist – also entweder den neu eingefügten Datensatz oder den Datensatz aus dem Konflikt-Update:
Führe das Statement zweimal aus. Beim ersten Mal wird eingefügt und ('visits', 1) zurückgegeben. Beim zweiten Mal greift der Konflikt, der Wert wird hochgezählt, und du bekommst ('visits', 2) zurück. So oder so: ein Statement rein, eine Zeile raus – ohne dass du dich vorher fragen musst, ob jetzt eingefügt oder aktualisiert wurde.
Das ist in SQLite das sauberste Muster für „gib mir den aktuellen Wert und leg ihn bei Bedarf an", ganz ohne zusätzliches Hin und Her.
Ein paar Stolperfallen
Ein paar Details, über die viele anfangs stolpern:
RETURNINGliefert beiINSERTundUPDATEimmer die Zeile nach der Änderung, beiDELETEdie Zeile vor der Änderung. Eine Syntax für die jeweils andere Seite gibt es nicht.- Die Reihenfolge der zurückgegebenen Zeilen ist nicht garantiert. Wenn sie wichtig ist, hänge clientseitig ein
ORDER BYan. RETURNINGlässt sich nicht in einer Subquery verwenden. Es ist eine Top-Level-Klausel des schreibenden Statements, kein Ausdruck.RETURNINGzeigt nicht die inBEFORE-Triggern modifizierten Daten – es liefert die tatsächlich geschriebenen Werte.AFTER-Trigger laufen zwischen dem Schreiben und dem Zurückgeben der Zeile.- Generierte Spalten und
DEFAULT-Werte sind im Ergebnis sichtbar. Genau deshalb istRETURNING *so praktisch, um zu sehen, was die Datenbank für dich befüllt hat.
Als Nächstes: CSV-Daten importieren
RETURNING ist super, wenn du eine oder wenige Zeilen schreibst und das Ergebnis sofort sehen willst. Wenn du dagegen Tausende Zeilen aus einer Datei lädst, sind die CSV-Import-Tools von SQLite das passendere Werkzeug – darum geht es auf der nächsten Seite.
Häufig gestellte Fragen
Unterstützt SQLite die RETURNING-Klausel überhaupt?
Ja, seit Version 3.35.0 (März 2021). Du kannst RETURNING an INSERT-, UPDATE- und DELETE-Statements anhängen und bekommst die betroffenen Zeilen zurück. Bei älteren SQLite-Versionen meckert der Parser – ein kurzes SELECT sqlite_version(); schafft Klarheit.
Wie bekomme ich in SQLite die ID einer gerade eingefügten Zeile?
Mit INSERT ... RETURNING id (oder RETURNING rowid, falls die Tabelle keinen expliziten Primärschlüssel hat). Den generierten Wert bekommst du direkt aus demselben Statement zurück – ein zusätzlicher Aufruf von last_insert_rowid() ist damit überflüssig.
Kann RETURNING mehrere Spalten zurückgeben?
Klar. Listest du die gewünschten Spalten einfach kommagetrennt auf, genau wie bei SELECT: RETURNING id, name, created_at. Mit RETURNING * bekommst du alle Spalten, und auch Ausdrücke sind erlaubt, etwa RETURNING id, price * quantity AS total.
Funktioniert RETURNING auch mit UPSERT bzw. ON CONFLICT?
Ja. INSERT ... ON CONFLICT ... DO UPDATE ... RETURNING ... liefert die Zeile zurück – egal, ob sie neu eingefügt oder durch die Konfliktauflösung aktualisiert wurde. So wickelst du einen UPSERT samt Rückgabe des Ergebnisses in einem einzigen Roundtrip ab.