SQLite Tidak Punya Tipe JSON, dan Itu Tidak Masalah
SQLite tidak punya tipe kolom khusus untuk JSON. JSON dimasukkan ke kolom TEXT biasa, dan sekumpulan fungsi bawaan, yang secara keseluruhan disebut ekstensi JSON1, tahu cara mem-parse, meng-query, dan mengubahnya. JSON1 sudah ada di setiap build SQLite modern, jadi tidak ada yang perlu diinstal.
Model berpikirnya: simpan dokumen sebagai teks, lalu pakai fungsi untuk melihat isinya.
Dua baris, masing-masing menyimpan dokumen JSON di kolom teks biasa. Sekarang kita butuh cara untuk menjangkau isi dokumen-dokumen itu.
Mengambil Field dengan json_extract dan ->>
json_extract(column, path) mengambil sebuah nilai dari dokumen JSON. Path diawali $ (root) dan memakai .field untuk key objek serta [i] untuk indeks array.
Menulis json_extract(data, '$.name') di mana-mana cepat terasa melelahkan, jadi SQLite memberimu dua operator:
->mengembalikan nilai dalam bentuk JSON (string kembali beserta tanda kutipnya).->>mengembalikan nilai SQL (teks atau angka, tanpa tanda kutip).
name_json kembali sebagai "Ada" (masih JSON), sedangkan name_text sebagai Ada. Pakai ->> jika kamu ingin nilai untuk dibandingkan atau ditampilkan. Pakai -> jika hasilnya akan kamu masukkan ke fungsi JSON lain.
Memfilter Berdasarkan Field JSON
Begitu kamu bisa mengambil nilai, kamu bisa memfilter. Ekspresinya ditaruh di klausa WHERE seperti biasa:
Ini berfungsi, tapi untuk tabel yang ukurannya lumayan, query ini lambat, karena setiap baris harus di-parse untuk menghitung kondisinya. Sebentar lagi kita akan memperbaikinya dengan index.
Membangun JSON: json_object dan json_array
Ke arah sebaliknya, kamu bisa menyusun JSON di dalam query:
json_object('k1', v1, 'k2', v2, ...) membangun sebuah objek. json_array(v1, v2, ...) membangun sebuah array. Keduanya praktis untuk merakit respons API langsung di SQL, dan keduanya bisa disarangkan tanpa masalah:
Memperbarui JSON: json_set, json_insert, json_replace
Tiga fungsi yang mirip satu sama lain mengubah dokumen JSON dan mengembalikan versi barunya:
json_set(doc, path, value): mengisi path itu, membuatnya jika belum ada, dan menimpanya jika sudah ada.json_insert(doc, path, value): hanya menambahkan jika path itu belum ada.json_replace(doc, path, value): hanya memperbarui jika path itu sudah ada.
Fungsi-fungsi ini tidak mengubah dokumen di tempat. Mereka mengembalikan dokumen baru, yang biasanya kamu tulis kembali dengan UPDATE:
Perhatikan bahwa json_set menerima beberapa pasangan path/nilai dalam satu panggilan. Untuk menghapus sebuah key, pakai json_remove(doc, path).
Memecah Array dengan json_each
json_each adalah fungsi bernilai tabel: fungsi ini menerima array (atau objek) JSON dan mengembalikan satu baris untuk setiap elemennya. Dengan begitu, "cari pengguna yang punya tag admin", yang canggung di SQL biasa, berubah menjadi join biasa:
Setiap baris dari users di-join dengan elemen-elemen array tags miliknya. json_each menyediakan kolom-kolom yang berguna, di antaranya key, value, type, dan fullkey. Saudaranya, json_tree, menelusuri seluruh dokumen secara rekursif, termasuk setiap node yang bersarang. Ini praktis untuk mencari di dokumen yang bentuknya tidak diketahui.
Memberi Index pada Field JSON
Query WHERE data ->> '$.active' = 1 di atas berfungsi, tapi SQLite harus mem-parse setiap baris untuk menghitung kondisinya. Untuk field yang sering kamu query, buat expression index:
Index harus memakai ekspresi yang persis sama dengan query-mu. Mencampur json_extract(data, '$.email') di index dengan data ->> '$.email' di query tidak akan cocok, dan index itu akan menganggur. Pilih satu bentuk dan konsisten memakainya.
Untuk field yang terus-menerus kamu query, generated column lebih enak dibaca:
Bagi penulis query, email terlihat seperti kolom biasa, tapi nilainya tetap sinkron dengan JSON secara otomatis.
Memvalidasi JSON
json_valid(text) mengembalikan 1 jika teksnya bisa di-parse sebagai JSON, dan 0 jika tidak. Pasangkan dengan constraint CHECK untuk menolak data yang salah saat ditulis:
Insert pertama berhasil; yang kedua gagal dengan error constraint. Tanpa pemeriksaan itu, JSON yang rusak akan diam-diam tersimpan di tabel sampai suatu panggilan json_extract meledak berbulan-bulan kemudian.
JSON vs JSONB
Sejak SQLite 3.45, ada representasi biner yang disebut JSONB: data yang sama, tapi sudah di-parse ke bentuk biner yang ringkas sehingga fungsi tidak perlu mem-parse ulang di setiap panggilan. Keluarga fungsi jsonb_* (jsonb_extract, jsonb_set, jsonb_object, ...) mengembalikan JSONB, bukan teks, dan kolom JSONB bisa di-query dengan operator yang sama.
Pakai JSON biasa (teks) jika kamu ingin dokumennya mudah dibaca manusia di hasil dump dan mudah diperiksa. Pakai JSONB jika tabelnya besar, sering di-query, dan beban parsing-nya memang terlihat saat profiling. Jangan beralih begitu saja: keterbacaan JSON biasa sangat berharga saat debugging.
Kapan JSON Adalah Pilihan yang Tepat
Kolom JSON unggul jika:
- Bentuknya berbeda-beda antarbaris (misalnya payload event, log audit, webhook integrasi).
- Kamu menyimpan cache respons API eksternal dan ingin menyimpannya utuh.
- Sebuah field jarang di-query dan hampir tidak pernah dipakai untuk memfilter.
Kolom JSON tidak cocok jika:
- Kamu memakai JSON untuk menghindari merancang skema. Jika setiap baris punya field yang sama, field itu seharusnya menjadi kolom.
- Kamu sering perlu memfilter atau join berdasarkan sebuah nilai. Kolom sungguhan dengan index akan selalu lebih cepat daripada pencarian lewat path JSON.
- Kamu sebenarnya butuh foreign key. JSON tidak punya integritas relasional.
Titik idealnya adalah menggabungkan keduanya: kolom skalar untuk field yang menentukan query dan constraint, ditambah satu kolom JSON di sampingnya untuk sisa data yang bervariasi.
Selanjutnya: Full-Text Search
JSON memberimu fleksibilitas di sisi penyimpanan. Halaman berikutnya membahas FTS5, mesin full-text search milik SQLite, yang memberimu pencarian teks sungguhan dengan peringkat dan penyorotan, jauh melampaui kemampuan LIKE.
Pertanyaan yang sering diajukan
Bagaimana SQLite menyimpan JSON?
SQLite tidak punya tipe khusus untuk JSON. JSON disimpan sebagai TEXT biasa. Ekstensi bawaan JSON1 (ikut dikompilasi secara default sejak 3.38) menyediakan fungsi seperti json_extract, json_set, dan json_each yang mem-parse dan mengolah teks itu. Sejak 3.45 juga ada format biner JSONB untuk akses berulang yang lebih cepat.
Bagaimana cara meng-query kolom JSON di SQLite?
Gunakan json_extract(column, '$.path') atau operator singkat ->>. Misalnya, SELECT data ->> '$.name' FROM users mengambil field name dari dokumen JSON yang disimpan di data. Path memakai $ untuk root, .field untuk key objek, dan [i] untuk indeks array.
Bisakah memberi index pada field JSON di SQLite?
Bisa. Buat expression index pada path yang diambil: CREATE INDEX idx_user_email ON users(json_extract(data, '$.email')). Query yang memakai ekspresi yang sama di klausa WHERE-nya akan memakai index itu. Untuk field yang sering di-query, generated column yang diberi index sering kali lebih rapi.
Apa perbedaan -> dan ->> di SQLite?
-> mengembalikan nilai JSON (masih dalam bentuk JSON, jadi string kembali dengan tanda kutip), sedangkan ->> mengembalikan nilai SQL (teks atau angka, tanpa tanda kutip). Pakai ->> jika kamu ingin nilai mentah untuk ditampilkan atau dibandingkan; pakai -> jika hasilnya akan dirangkai ke operasi JSON berikutnya.