Menu

JSON w SQLite: json_extract, json_set i json_each

Jak SQLite przechowuje i odpytuje JSON: wyciąganie pól, aktualizacja wartości, rozwijanie tablic przez json_each i indeksowanie ścieżek JSON dla szybkości.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

SQLite nie ma typu JSON i to nie problem

SQLite nie ma osobnego typu kolumny dla JSON. JSON trafia do zwykłej kolumny TEXT, a zestaw wbudowanych funkcji, nazywany razem rozszerzeniem JSON1, potrafi go parsować, odpytywać i modyfikować. JSON1 jest dostępne w każdej współczesnej wersji SQLite, więc nie trzeba nic instalować.

Model myślowy: przechowuj dokument jako tekst, a do zaglądania do środka używaj funkcji.

Dwa wiersze, każdy z dokumentem JSON w zwykłej kolumnie tekstowej. Teraz potrzebujemy sposobów, żeby sięgnąć do wnętrza tych dokumentów.

Wyciąganie pól przez json_extract i ->>

json_extract(column, path) wyciąga wartość z dokumentu JSON. Ścieżka zaczyna się od $ (korzeń) i używa .field dla kluczy obiektu oraz [i] dla indeksów tablicy.

Pisanie wszędzie json_extract(data, '$.name') szybko się nudzi, więc SQLite daje dwa operatory:

  • -> zwraca wartość zakodowaną jako JSON (napisy wracają z cudzysłowami).
  • ->> zwraca wartość SQL (tekst lub liczbę, bez cudzysłowów).

name_json wraca jako "Ada" (nadal JSON), a name_text jako Ada. Używaj ->>, gdy chcesz wartości do porównania lub wyświetlenia. Używaj ->, gdy wynik trafi do kolejnej funkcji JSON.

Filtrowanie po polach JSON

Skoro umiesz wyciągać, umiesz też filtrować. Wyrażenie trafia do klauzuli WHERE jak każde inne:

To działa, ale w tabeli jakiejkolwiek wielkości jest wolne: każdy wiersz trzeba sparsować, żeby sprawdzić warunek. Za chwilę naprawimy to indeksem.

Budowanie JSON: json_object i json_array

W drugą stronę możesz budować JSON w samym zapytaniu:

json_object('k1', v1, 'k2', v2, ...) buduje obiekt. json_array(v1, v2, ...) buduje tablicę. Przydają się do składania odpowiedzi API bezpośrednio w SQL i bez problemu się zagnieżdżają:

Aktualizacja JSON: json_set, json_insert, json_replace

Trzy blisko spokrewnione funkcje modyfikują dokument JSON i zwracają jego nową wersję:

  • json_set(doc, path, value): ustawia ścieżkę, tworząc ją, jeśli jej brak, i nadpisując, jeśli istnieje.
  • json_insert(doc, path, value): wstawia tylko wtedy, gdy ścieżka jeszcze nie istnieje.
  • json_replace(doc, path, value): aktualizuje tylko wtedy, gdy ścieżka już istnieje.

Te funkcje nie zmieniają dokumentu w miejscu, tylko zwracają nowy, który zwykle zapisujesz z powrotem przez UPDATE:

Zwróć uwagę, że json_set przyjmuje w jednym wywołaniu kilka par ścieżka/wartość. Żeby usunąć klucz, użyj json_remove(doc, path).

Rozwijanie tablic przez json_each

json_each to funkcja tablicowa: przyjmuje tablicę (lub obiekt) JSON i zwraca jeden wiersz na element. Dzięki temu „znajdź użytkowników z tagiem admin”, niewygodne w czystym SQL, staje się zwykłym złączeniem:

Każdy wiersz z users jest łączony z elementami swojej tablicy tags. json_each udostępnia przydatne kolumny, m.in. key, value, type i fullkey. Jego rodzeństwo, json_tree, rekurencyjnie przechodzi przez cały dokument, łącznie z każdym zagnieżdżonym węzłem, co przydaje się przy przeszukiwaniu dokumentów o nieznanym kształcie.

Indeksowanie pól JSON

Zapytanie WHERE data ->> '$.active' = 1 z przykładu wyżej działa, ale SQLite musi sparsować każdy wiersz, żeby sprawdzić warunek. Dla pól, po które często sięgasz, zbuduj indeks na wyrażeniu:

Indeks musi używać dokładnie tego samego wyrażenia co zapytanie. json_extract(data, '$.email') w indeksie i data ->> '$.email' w zapytaniu nie zostaną dopasowane, a indeks będzie leżał nieużywany. Wybierz jedną formę i się jej trzymaj.

Dla pól odpytywanych bez przerwy lepiej czyta się kolumna generowana:

Dla piszących zapytania email wygląda jak zwykła kolumna, a przy tym automatycznie pozostaje zsynchronizowane z JSON.

Walidacja JSON

json_valid(text) zwraca 1, jeśli tekst da się sparsować jako JSON, a w przeciwnym razie 0. Połącz to z ograniczeniem CHECK, żeby odrzucać złe dane już przy zapisie:

Pierwsze wstawienie się udaje, drugie kończy się błędem ograniczenia. Bez tego sprawdzenia źle sformatowany JSON spokojnie leży w tabeli, aż kilka miesięcy później jakieś wywołanie json_extract wybuchnie.

JSON a JSONB

Od SQLite 3.45 istnieje binarna reprezentacja o nazwie JSONB: te same dane, wstępnie sparsowane do zwartej postaci binarnej, żeby funkcje nie parsowały ich przy każdym wywołaniu. Rodzina funkcji jsonb_* (jsonb_extract, jsonb_set, jsonb_object, ...) zwraca JSONB zamiast tekstu, a kolumny JSONB można odpytywać tymi samymi operatorami.

Używaj zwykłego JSON (tekstu), gdy chcesz, żeby dokumenty były czytelne w zrzutach i łatwe do podejrzenia. Sięgaj po JSONB, gdy tabela jest duża, często odpytywana, a koszt parsowania faktycznie widać w profilowaniu. Nie przechodź na nie domyślnie: czytelność zwykłego JSON jest bardzo cenna przy debugowaniu.

Kiedy JSON to dobry wybór

Kolumny JSON sprawdzają się, gdy:

  • Kształt danych różni się między wierszami (np. dane zdarzeń, logi audytowe, webhooki integracji).
  • Zapisujesz w cache odpowiedź zewnętrznego API i chcesz zachować ją w nienaruszonym stanie.
  • Pole jest rzadko odpytywane i prawie nigdy nie filtrujesz po nim.

Nie pasują, gdy:

  • Używasz JSON, żeby uniknąć projektowania schematu. Jeśli każdy wiersz ma te same pola, to są kolumny.
  • Musisz często filtrować albo łączyć po wartości. Prawdziwa kolumna z indeksem za każdym razem wyprzedzi wyszukiwanie po ścieżce JSON.
  • Potrzebujesz kluczy obcych. JSON nie ma integralności relacyjnej.

Najlepiej sprawdza się połączenie obu: kolumny skalarne dla pól, od których zależą zapytania i ograniczenia, a obok kolumna JSON na długi ogon zmiennych danych.

Dalej: wyszukiwanie pełnotekstowe

JSON daje elastyczność po stronie przechowywania. Następna strona omawia FTS5, silnik wyszukiwania pełnotekstowego w SQLite, który daje prawdziwe wyszukiwanie tekstu z rankingiem i wyróżnianiem, daleko wykraczające poza możliwości LIKE.

Najczęściej zadawane pytania

Jak SQLite przechowuje JSON?

SQLite nie ma osobnego typu JSON: JSON jest przechowywany jako zwykły TEXT. Wbudowane rozszerzenie JSON1 (domyślnie wkompilowane od wersji 3.38) udostępnia funkcje takie jak json_extract, json_set i json_each, które parsują ten tekst i na nim operują. Od wersji 3.45 jest też binarny format JSONB do szybszego wielokrotnego dostępu.

Jak odpytać kolumnę JSON w SQLite?

Użyj json_extract(column, '$.path') albo skróconego operatora ->>. Na przykład SELECT data ->> '$.name' FROM users wyciąga pole name z dokumentu JSON zapisanego w data. Ścieżki używają $ dla korzenia, .field dla kluczy obiektu i [i] dla indeksów tablicy.

Czy można zaindeksować pole JSON w SQLite?

Tak: utwórz indeks na wyrażeniu z wyciąganą ścieżką: CREATE INDEX idx_user_email ON users(json_extract(data, '$.email')). Zapytania, które używają tego samego wyrażenia w klauzuli WHERE, skorzystają z indeksu. Dla często odpytywanych pól kolumna generowana z indeksem bywa czytelniejsza.

Jaka jest różnica między -> a ->> w SQLite?

-> zwraca wartość JSON (nadal zakodowaną jako JSON, więc napisy wracają w cudzysłowach), a ->> zwraca wartość SQL (tekst lub liczbę, bez cudzysłowów). Używaj ->>, gdy chcesz surowej wartości do wyświetlenia lub porównania, a ->, gdy łączysz kolejne operacje JSON.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ