Neden JSON?
Uygulama olayları, API yanıtları ve esnek nitelikler çoğu zaman JSON olarak gelir. SQL:2016 ile JSON desteği standarda girdi; ama her SQL türü kendi fonksiyonlarını kullanır.
| İşlem | SQL Server | PostgreSQL | MySQL | Oracle | SQLite |
|---|---|---|---|---|---|
| Alan oku | JSON_VALUE(j, '$.a.b') | j->'a'->>'b' | j->>'$.a.b' | JSON_VALUE(j, '$.a.b') | json_extract(j, '$.a.b') |
| Diziyi satırlara aç | OPENJSON | jsonb_array_elements | JSON_TABLE | JSON_TABLE | json_each |
| JSON üret | FOR JSON PATH, JSON_OBJECT (2022) | json_build_object, json_agg | JSON_OBJECT, JSON_ARRAYAGG | JSON_OBJECT, JSON_ARRAYAGG | json_object, json_group_array |
WITH olaylar(id, payload) AS (
VALUES (1, '{"user": {"id": 7, "city": "Bursa"}, "total": 420}'),
(2, '{"user": {"id": 9, "city": "Konya"}, "total": 2499}')
)
SELECT id,
json_extract(payload, '$.user.city') AS sehir,
json_extract(payload, '$.total') AS toplam
FROM olaylar;
SQL ile JSON üretmek
SELECT json_object('id', customer_id, 'ad', first_name, 'sehir', city) AS musteri_json
FROM customers
ORDER BY customer_id
LIMIT 3;
Derinleş: JSON ile çalışırken SQL türleri arası farklar
JSON fonksiyonlarının adları SQL türüne göre değişir, ama asıl farklar daha derindedir: saklama tipi, dönen değerin tipi ve dizileri satırlara açma yöntemi.
| SQL türü | Saklama | Not |
|---|---|---|
| PostgreSQL | json (metin olarak) ve jsonb (ikili, indekslenebilir) | Analitik için jsonb + GIN indeks |
| MySQL | JSON tipi (doğrulanır, ikili saklanır) | Üretilmiş (generated) sütunla indekslenir |
| SQL Server | Ayrı tip yok (2025 öncesi): NVARCHAR(MAX) | ISJSON() ile CHECK kısıtı ekle |
| Oracle | JSON (21c+), öncesinde CLOB + IS JSON | Nokta yazımı: t.j.user.city |
| BigQuery | JSON tipi ya da STRING | JSON_VALUE, JSON_QUERY_ARRAY |
| SQLite | Metin | json_extract sayıyı sayı olarak döndürür |
Dönen tip tuzağı
PostgreSQL'de ->>, SQL Server ve Oracle'da JSON_VALUE her zaman metin döndürür. '9' > '10' metin karşılaştırmasında doğrudur; sıralama ve filtrede sayısal alanı mutlaka CAST et:
WITH t AS (
SELECT restaurant_id,
json_object('ad', name, 'sehir', city, 'komisyon', commission_pct) AS j
FROM lk_restaurants
)
SELECT json_extract(j, '$.sehir') AS sehir, COUNT(*) AS restoran
FROM t
WHERE CAST(json_extract(j, '$.komisyon') AS REAL) >= 20
GROUP BY json_extract(j, '$.sehir')
ORDER BY sehir;
Diziyi satırlara açmak
Olay verisinde etiketler çoğu zaman bir dizi olarak gelir. Etiket başına sayım için dizi satırlara açılır: SQLite'ta json_each, SQL Server'da CROSS APPLY OPENJSON.
WITH olaylar(id, payload) AS (
VALUES (1, '{"user": 7, "etiketler": ["indirim", "mobil"]}'),
(2, '{"user": 9, "etiketler": ["mobil"]}'),
(3, '{"user": 7, "etiketler": []}')
)
SELECT j.value AS etiket, COUNT(*) AS olay
FROM olaylar o, json_each(json_extract(o.payload, '$.etiketler')) j
GROUP BY j.value
ORDER BY olay DESC, etiket;
-- PostgreSQL SELECT e.etiket, COUNT(*) FROM olaylar o CROSS JOIN LATERAL jsonb_array_elements_text(o.payload -> 'etiketler') AS e(etiket) GROUP BY e.etiket; -- MySQL 8 / Oracle SELECT jt.etiket, COUNT(*) FROM olaylar o, JSON_TABLE(o.payload, '$.etiketler[*]' COLUMNS (etiket VARCHAR(50) PATH '$')) jt GROUP BY jt.etiket;
Boş dizili 3 numaralı olay sonuçta görünmez; çünkü açılacak eleman yoktur (iç birleştirme gibi davranır). “Etiketsiz olay” sayısı da gerekiyorsa ayrıca hesapla ya da LEFT JOIN LATERAL / OUTER APPLY kullan.
Performans: JSON içindeki bir alana göre sık filtreleme yapılıyorsa her satırda metni yeniden ayrıştırmak pahalıdır. PostgreSQL'de jsonb sütununa GIN indeks, MySQL'de alanı üretilmiş sütuna çıkarıp indeksleme (ALTER TABLE t ADD sehir VARCHAR(50) AS (j->>'$.sehir'), ADD INDEX (sehir)), SQL Server'da hesaplanmış sütun + indeks kullanılır.