Menu

SQLite JSON: json_extract, json_set & json_each erklärt

Wie SQLite JSON speichert und abfragt: Felder mit json_extract auslesen, Werte aktualisieren, Arrays per json_each entpacken und JSON-Pfade indexieren.

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

SQLite hat keinen JSON-Typ – und das ist auch gut so

SQLite kennt keinen eigenen JSON-Spaltentyp. JSON landet einfach in einer normalen TEXT-Spalte, und eine Reihe eingebauter Funktionen – zusammengefasst als JSON1-Erweiterung – weiß, wie man darin liest, sucht und schreibt. JSON1 ist in jedem modernen SQLite-Build dabei, du musst also nichts nachinstallieren.

Die Denkweise dahinter: Speichere das Dokument als Text und greif mit Funktionen darauf zu, um reinzuschauen.

Zwei Zeilen, jede mit einem JSON-Dokument in einer ganz normalen Textspalte. Jetzt brauchen wir Möglichkeiten, an die Werte in diesen Dokumenten heranzukommen.

Felder auslesen mit json_extract und dem ->>-Operator

json_extract(spalte, pfad) holt einen Wert aus einem JSON-Dokument heraus. Der Pfad beginnt mit $ (der Wurzel) und nutzt .feldname für Objektschlüssel sowie [i] für Array-Indizes.

Wer ständig json_extract(data, '$.name') schreiben muss, hat irgendwann die Nase voll. Genau dafür gibt es in SQLite zwei praktische Operatoren:

  • -> liefert einen JSON-codierten Wert zurück (Strings kommen also samt Anführungszeichen).
  • ->> liefert einen reinen SQL-Wert (Text oder Zahl, ohne Anführungszeichen).

name_json liefert "Ada" zurück (immer noch JSON), name_text dagegen Ada. Nimm ->>, wenn du den Wert vergleichen oder anzeigen willst. Nimm ->, wenn das Ergebnis als Eingabe für eine weitere JSON-Funktion dient.

Nach JSON-Feldern filtern

Wer extrahieren kann, kann auch filtern. Der Ausdruck wandert einfach in die WHERE-Klausel wie jede andere Bedingung auch:

Das funktioniert zwar, wird aber bei größeren Tabellen schnell langsam – schließlich muss jede Zeile geparst werden, um das Prädikat auszuwerten. Wie du das mit einem Index in den Griff bekommst, sehen wir gleich.

JSON erzeugen mit json_object und json_array

Umgekehrt geht es genauso: Du kannst JSON direkt innerhalb einer Abfrage zusammenbauen:

json_object('k1', v1, 'k2', v2, ...) erzeugt ein Objekt, json_array(v1, v2, ...) ein Array. Praktisch, wenn du API-Antworten direkt in SQL zusammenbauen willst – und verschachteln lassen sie sich problemlos:

JSON aktualisieren: json_set, json_insert und json_replace

In SQLite gibt es drei eng verwandte Funktionen, mit denen du ein JSON-Dokument verändern und die neue Fassung zurückbekommen kannst:

  • json_set(doc, path, value) — setzt den Wert am angegebenen Pfad. Existiert der Pfad nicht, wird er angelegt; existiert er bereits, wird er überschrieben.
  • json_insert(doc, path, value) — fügt nur dann ein, wenn der Pfad noch nicht vorhanden ist.
  • json_replace(doc, path, value) — aktualisiert nur, wenn der Pfad bereits existiert.

Wichtig: Diese Funktionen verändern das Dokument nicht direkt, sondern liefern eine neue Version zurück. Das Ergebnis schreibst du anschließend typischerweise mit UPDATE zurück in die Tabelle:

json_set akzeptiert übrigens mehrere Pfad/Wert-Paare in einem einzigen Aufruf. Um einen Schlüssel zu entfernen, nimmst du json_remove(doc, path).

JSON-Arrays auflösen mit json_each

json_each ist eine Table-Valued Function: Du übergibst ihr ein JSON-Array (oder ein Objekt) und bekommst pro Element eine Zeile zurück. Damit wird aus "alle User mit dem Tag admin finden" – in reinem SQL eher umständlich – ein ganz normaler Join:

Jede Zeile aus users wird mit den Elementen ihres tags-Arrays verknüpft. json_each liefert dabei nützliche Spalten wie key, value, type und fullkey. Das Pendant json_tree läuft rekursiv durch das gesamte Dokument und nimmt jeden verschachtelten Knoten mit – praktisch, wenn du Dokumente mit unbekannter Struktur durchsuchen willst.

Index auf JSON-Feld in SQLite anlegen

Die obige Abfrage WHERE data ->> '$.active' = 1 funktioniert zwar, aber SQLite muss jede Zeile parsen, um das Prädikat auszuwerten. Für Felder, die du häufig abfragst, lohnt sich ein Ausdrucksindex (Expression Index):

Der Index muss exakt denselben Ausdruck verwenden wie deine Query. Wenn du im Index json_extract(data, '$.email') schreibst, in der Query aber data ->> '$.email', greift der Index nicht – er liegt einfach ungenutzt herum. Also: eine Schreibweise wählen und konsequent dabei bleiben.

Für Felder, die du ständig abfragst, ist eine generierte Spalte (Generated Column) übersichtlicher:

email sieht für alle, die Queries schreiben, aus wie eine ganz normale Spalte – bleibt aber automatisch synchron mit dem JSON.

JSON validieren in SQLite

json_valid(text) gibt 1 zurück, wenn sich der Text als JSON parsen lässt, sonst 0. Kombiniert mit einer CHECK-Bedingung kannst du fehlerhafte Daten direkt beim Schreiben blockieren:

Beim ersten Insert klappt alles, der zweite scheitert mit einem Constraint-Fehler. Ohne diesen Check würde fehlerhaftes JSON unbemerkt in der Tabelle schlummern – bis irgendwann Monate später ein json_extract-Aufruf um die Ohren fliegt.

JSON vs. JSONB in SQLite

Seit SQLite 3.45 gibt es mit JSONB eine binäre Darstellung – dieselben Daten, aber bereits geparst und in einem kompakten Binärformat abgelegt, sodass die Funktionen sie nicht bei jedem Aufruf neu parsen müssen. Die Funktionsfamilie jsonb_* (jsonb_extract, jsonb_set, jsonb_object, ...) liefert JSONB statt Text zurück, und JSONB-Spalten lassen sich mit denselben Operatoren abfragen.

Greife zu reinem JSON (als Text), wenn deine Dokumente in Dumps für Menschen lesbar bleiben und sich leicht inspizieren lassen sollen. JSONB lohnt sich dann, wenn eine Tabelle groß ist, häufig abgefragt wird und der Parsing-Overhead im Profiling tatsächlich auftaucht. Wechsle nicht reflexartig – die Lesbarkeit von Plain JSON ist beim Debuggen Gold wert.

Wann JSON in SQLite wirklich Sinn ergibt

JSON-Spalten spielen ihre Stärken aus, wenn:

  • Die Struktur von Zeile zu Zeile variiert (z. B. Event-Payloads, Audit-Logs, Webhooks aus Integrationen).
  • Du eine externe API-Antwort cachen und sie unverändert behalten willst.
  • Ein Feld nur selten abgefragt und so gut wie nie zum Filtern genutzt wird.

Schlecht passen sie, wenn:

  • Du JSON nur einsetzt, um dir einen sauberen Schema-Entwurf zu sparen. Wenn jede Zeile dieselben Felder hat, gehören diese in eigene Spalten.
  • Du regelmäßig nach einem Wert filtern oder darauf joinen musst. Eine echte Spalte mit Index ist einem JSON-Pfad-Lookup immer überlegen.
  • Du eigentlich Fremdschlüssel bräuchtest. JSON kennt keine relationale Integrität.

Der Sweet Spot ist die Kombination aus beidem: skalare Spalten für die Felder, die Queries und Constraints tragen, daneben eine JSON-Spalte für den Long Tail an variablen Daten.

Weiter geht's: Volltextsuche

JSON gibt dir Flexibilität auf der Speicherseite. Die nächste Seite widmet sich FTS5 – der Volltextsuch-Engine von SQLite. Damit bekommst du echte Textsuche mit Ranking und Highlighting, weit jenseits dessen, was LIKE leisten kann.

Häufig gestellte Fragen

Wie speichert SQLite JSON-Daten?

SQLite hat keinen eigenen JSON-Datentyp – JSON landet schlicht als TEXT in der Spalte. Die JSON1-Erweiterung, die seit Version 3.38 standardmäßig mitkompiliert wird, stellt Funktionen wie json_extract, json_set und json_each bereit, um diesen Text zu parsen und damit zu arbeiten. Seit 3.45 gibt es zusätzlich das binäre JSONB-Format, das wiederholte Zugriffe deutlich beschleunigt.

Wie frage ich eine JSON-Spalte in SQLite ab?

Entweder über json_extract(spalte, '$.pfad') oder über die Kurzschreibweise mit dem ->>-Operator. Beispiel: SELECT data ->> '$.name' FROM users zieht das Feld name aus dem JSON-Dokument in der Spalte data. Pfade beginnen mit $ für die Wurzel, .feld adressiert Objektschlüssel und [i] greift auf Array-Elemente zu.

Kann ich ein JSON-Feld in SQLite indexieren?

Ja, und zwar über einen Ausdrucksindex auf dem extrahierten Pfad: CREATE INDEX idx_user_email ON users(json_extract(data, '$.email')). Sobald eine Query denselben Ausdruck im WHERE verwendet, greift der Index. Wenn du ein Feld sehr oft abfragst, ist eine generierte Spalte mit Index oft die sauberere Lösung.

Was ist der Unterschied zwischen -> und ->> in SQLite?

-> liefert einen JSON-Wert zurück (also weiterhin JSON-kodiert – Strings kommen z. B. mit Anführungszeichen), während ->> einen reinen SQL-Wert (Text oder Zahl, ohne Anführungszeichen) zurückgibt. Für Anzeige oder Vergleiche nimmst du ->>, für verkettete JSON-Operationen bleibst du bei ->.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S