SQLite'ta JSON Tipi Yok — ve Bu Hiç Sorun Değil
SQLite'ta JSON için ayrı bir kolon tipi bulunmuyor. JSON verisini sıradan bir TEXT kolonuna yazıyorsunuz; geri kalanı ise toplu olarak JSON1 eklentisi diye anılan yerleşik fonksiyonların işi — bu fonksiyonlar veriyi okuyor, sorguluyor ve güncelliyor. JSON1, modern SQLite sürümlerinin hepsiyle birlikte geldiği için ekstra bir kurulum yapmanıza gerek yok.
Mantığı şöyle düşünün: dokümanı metin olarak saklayın, içine bakmak için fonksiyonları kullanın.
İki satır var, her biri düz bir metin kolonunda bir JSON belgesi tutuyor. Şimdi bu belgelerin içine uzanmanın yollarına ihtiyacımız var.
json_extract ve ->> ile alan çıkarma
json_extract(column, path) bir JSON belgesinden değer çekip getirir. Path $ ile başlar (kök) ve nesne anahtarları için .alan, dizi indeksleri için [i] kullanır.
Her seferinde json_extract(data, '$.name') yazmaktan sıkıldığınızı düşünerek SQLite size iki operatör sunuyor:
->JSON kodlanmış değer döndürür (stringler tırnak işaretleriyle birlikte gelir).->>SQL değeri döndürür (metin ya da sayı, tırnaksız).
name_json sonucu "Ada" şeklinde döner (yani hâlâ JSON), name_text ise düz Ada olarak gelir. Bir değeri karşılaştırmak ya da ekrana basmak istiyorsanız ->> kullanın. Sonucu başka bir JSON fonksiyonuna girdi olarak vereceksiniz -> tercih edin.
JSON Alanlarına Göre Filtreleme
Değer çıkarabildiğinize göre artık filtreleme de yapabilirsiniz. İfadeyi, diğer koşullarda olduğu gibi WHERE cümlesine yazmanız yeterli:
Bu çalışıyor, ama tablo biraz büyüdüğünde işler yavaşlıyor — çünkü koşulu değerlendirmek için her satırın tek tek parse edilmesi gerekiyor. Birazdan bunu bir indeksle çözeceğiz.
JSON üretmek: json_object ve json_array
Tersine, sorgu içinde JSON da oluşturabilirsiniz:
json_object('k1', v1, 'k2', v2, ...) ile bir nesne, json_array(v1, v2, ...) ile de bir dizi oluşturursunuz. API yanıtlarını doğrudan SQL içinde derlemek için bire birdir; üstelik iç içe kullanmaktan da gocunmazlar:
JSON Güncelleme: json_set, json_insert, json_replace
Bir JSON belgesini değiştirip yeni halini döndüren, birbirine çok yakın üç fonksiyon var:
json_set(doc, path, value)— yolu ayarlar; yol yoksa oluşturur, varsa üzerine yazar.json_insert(doc, path, value)— yalnızca yol mevcut değilse ekler.json_replace(doc, path, value)— yalnızca yol zaten varsa günceller.
Bu fonksiyonlar belgeyi yerinde değiştirmez; size yeni bir belge döndürür. Genelde sonucu UPDATE ile geri yazarsınız:
json_set fonksiyonunun tek çağrıda birden fazla path/value çifti alabildiğini unutmayın. Bir anahtarı silmek için json_remove(doc, path) kullanın.
json_each ile Dizileri Satırlara Açmak
json_each, tablo döndüren bir fonksiyondur: girdi olarak bir JSON dizisi (veya nesnesi) alır ve her bir eleman için bir satır üretir. Bu sayede düz SQL'de epey zahmetli olan "admin etiketine sahip kullanıcıları bul" gibi sorgular, sıradan bir join'e dönüşür:
users tablosundaki her satır, kendi tags dizisinin elemanlarıyla join'lenir. json_each; key, value, type ve fullkey gibi işine yarayacak sütunları sana açar. Kardeşi json_tree ise belgeyi baştan sona, iç içe geçmiş tüm düğümleri dahil ederek dolaşır — yapısını önceden bilmediğiniz belgelerde arama yaparken çok kullanışlıdır.
SQLite JSON Kolonlarında İndeksleme
Yukarıdaki WHERE data ->> '$.active' = 1 sorgusu çalışır çalışmasına, ama SQLite koşulu değerlendirmek için her satırı tek tek ayrıştırmak zorunda kalır. Sıkça sorguladığınız alanlar için bir ifade indeksi (expression index) oluştur:
İndeks, sorgunuzdaki ifadenin birebir aynısını kullanmak zorunda. İndekste json_extract(data, '$.email'), sorguda ise data ->> '$.email' yazarsanız ikisi eşleşmez ve indeks boşu boşuna durur — birini seçip ona sadık kalın.
Sürekli sorguladığınız alanlar için generated column çok daha okunaklı bir çözüm:
email alanı sorgu yazan biri için sıradan bir kolon gibi görünür ama arka planda JSON ile otomatik olarak senkron kalır.
JSON doğrulama
json_valid(text) fonksiyonu, metin geçerli bir JSON ise 1, değilse 0 döner. Bunu bir CHECK kısıtıyla birleştirirseniz, hatalı veriyi daha yazma anında reddedebilirsiniz:
İlk INSERT çalışır; ikincisi ise CONSTRAINT hatası verir. Bu kontrol olmadan, bozuk JSON tabloda sessiz sedasız oturur ve aylar sonra bir json_extract çağrısı patlayana kadar kimsenin haberi olmaz.
JSON ve JSONB farkı
SQLite 3.45 ile birlikte JSONB adında ikili (binary) bir format geldi — aynı veri, ama önceden ayrıştırılmış kompakt bir biçimde tutuluyor. Böylece fonksiyonlar her çağrıda baştan parse etmek zorunda kalmıyor. jsonb_* ailesindeki fonksiyonlar (jsonb_extract, jsonb_set, jsonb_object, ...) metin yerine JSONB döndürür ve JSONB kolonları da aynı operatörlerle sorgulanabilir.
Dökümlerde okunaklı, gözle inceleyebileceğiniz belgeler istiyorsanız düz JSON (text) kullanın. Tablo büyüdüğünde, sorgu trafiği arttığında ve profilde parse maliyeti gerçekten göze batmaya başladığında JSONB'ye geçin. Varsayılan olarak JSONB seçmeyin — düz JSON'un hata ayıklama sırasındaki okunabilirliği çok değerli.
JSON Ne Zaman Doğru Tercihtir
JSON kolonları şu durumlarda parlar:
- Satırdan satıra şekil değişiyorsa (event payload'ları, denetim logları, entegrasyon webhook'ları gibi).
- Harici bir API cevabını olduğu gibi saklamak istiyorsanız.
- Alan nadiren sorgulanıyor ve neredeyse hiç filtreye girmiyorsa.
Şu durumlarda ise yanlış seçimdir:
- Şema tasarımından kaçmak için JSON'a sığınıyorsanız. Her satırda aynı alanlar varsa, bunlar kolon olmalı.
- Bir değer üzerinden sık sık filtre veya join yapmanız gerekiyorsa. İndeksli gerçek bir kolon, JSON path araması karşısında her zaman önde gider.
- Foreign key ihtiyacınız varsa. JSON'da ilişkisel bütünlük yoktur.
İşin tatlı noktası ikisini birlikte kullanmak: sorguları ve kısıtları yönlendiren alanlar için skaler kolonlar, değişken verinin uzun kuyruğu için yanlarına bir JSON kolonu.
Sırada: Full-Text Search
JSON size depolama tarafında esneklik sağlıyor. Sıradaki sayfa FTS5'i — SQLite'ın tam metin arama motorunu — anlatıyor; LIKE ile yapabileceklerinizin çok ötesinde, sıralama ve vurgulama da sunan gerçek bir metin araması elde ediyorsunuz.
Sıkça Sorulan Sorular
SQLite JSON verisini nasıl saklar?
SQLite'ta ayrı bir JSON tipi yok — JSON, düz TEXT olarak saklanır. 3.38'den itibaren varsayılan olarak gelen JSON1 eklentisi; json_extract, json_set ve json_each gibi fonksiyonlarla bu metni ayrıştırıp üzerinde işlem yapmanı sağlar. 3.45 sürümüyle birlikte tekrar tekrar erişimde daha hızlı çalışan ikili JSONB formatı da geldi.
SQLite'ta bir JSON kolonu nasıl sorgulanır?
Ya json_extract(column, '$.path') fonksiyonunu ya da onun kısayolu olan ->> operatörünü kullanabilirsin. Mesela SELECT data ->> '$.name' FROM users sorgusu, data kolonundaki JSON dokümanından name alanını çeker. Yollarda kök için $, nesne anahtarları için .alan, dizi indeksleri için ise [i] kullanılır.
SQLite'ta bir JSON alanı indekslenebilir mi?
Evet — çıkardığın yol üzerine bir ifade indeksi (expression index) oluşturman yeterli: CREATE INDEX idx_user_email ON users(json_extract(data, '$.email')). WHERE koşulunda aynı ifadeyi kullanan sorgular bu indeksi otomatik olarak kullanır. Sık sorgulanan alanlar için indeksli bir generated column genelde daha temiz bir çözüm olur.
SQLite'ta -> ile ->> arasındaki fark nedir?
-> bir JSON değeri döndürür (yani sonuç hâlâ JSON kodlu — string'ler tırnaklı gelir), ->> ise düz bir SQL değeri döndürür (tırnaksız metin ya da sayı). Ekrana basmak veya karşılaştırmak için ham değeri istiyorsan ->> kullan; üst üste JSON işlemi zincirleyeceksen -> daha mantıklı.