JSON Fonksiyonları
Giriş — İlişkisel Veritabanında Yapısal Olmayan Veri
İlişkisel veritabanları sütunlar ve satırlardan oluşur — her sütunun tipi bellidir, her satır aynı yapıdadır. Ama gerçek dünyada her veri bu kalıba uymaz: bir ürünün özellik listesi (renk, beden, ağırlık, voltaj...) ürün tipine göre değişir. Bir siparişteki adres bilgisi iç içe yapıda olabilir. Kullanıcı tercihleri esnek bir yapıda saklanmak istenir.
Geleneksel yaklaşım: her olası özellik için bir sütun ekle (color VARCHAR, size VARCHAR, weight DECIMAL...). Ama 50 farklı özellik varsa ve her ürün sadece 3-4'ünü kullanıyorsa, tabloda 46 boş sütun olur — hem israf hem bakım kabusu.
İşte JSON veri tipi tam bu sorunu çözer. MySQL 5.7.8+ ve PostgreSQL 9.2+ sürümlerinde JSON, birinci sınıf bir veri tipi olarak desteklenir. İlişkisel yapının gücünü korurken, esnek yapıdaki verileri JSON sütunlarında saklayabilirsin.
🎯 Analoji: JSON sütununu, bir çekmecedeki dosya zarfı gibi düşün. Çekmece (tablo) düzenli ve etiketli: sipariş numarası, müşteri, tarih. Ama zarfın içinde (JSON sütunu) her sipariş için farklı belgeler olabilir — biri fatura, biri kargo bilgisi, biri özel notlar. Zarfın dışı standart, içi esnek.
JSON Veri Tipi
-- JSON sütunu olan tablo
CREATE TABLE products_v2 (
product_id INT PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(200) NOT NULL,
price DECIMAL(10,2) NOT NULL,
-- Esnek özellikler JSON'da
attributes JSON,
-- Ayarlar JSON'da
settings JSON DEFAULT '{}'
);
-- JSON veri ekleme
INSERT INTO products_v2 (product_name, price, attributes) VALUES
('iPhone 15', 55000, '{
"color": "Mavi",
"storage": "256GB",
"ram": "6GB",
"screen_size": 6.1,
"5g": true,
"colors_available": ["Mavi", "Siyah", "Pembe", "Sarı"]
}'),
('SQL Kitabı', 120, '{
"author": "Tolgahan Çelik",
"pages": 450,
"isbn": "978-123-456",
"format": ["Karton Kapak", "E-Kitap"],
"language": "Türkçe"
}'),
('Koşu Ayakkabısı', 2500, '{
"brand": "Nike",
"sizes": [38, 39, 40, 41, 42, 43, 44],
"color": "Siyah/Beyaz",
"material": "Mesh",
"waterproof": false
}');MySQL, JSON sütununa geçersiz JSON yazılmasını engeller:
-- ❌ Geçersiz JSON — hata verir
INSERT INTO products_v2 (product_name, price, attributes)
VALUES ('Test', 100, '{invalid json}');
-- ERROR: Invalid JSON textJSON_EXTRACT — JSON'dan Değer Çekme
En temel fonksiyon: JSON belgesinden belirli bir değeri çıkarmak.
-- Temel kullanım
SELECT
product_name,
JSON_EXTRACT(attributes, '$.color') AS color,
JSON_EXTRACT(attributes, '$.storage') AS storage
FROM products_v2;+-------------------+---------+-----------+
| product_name | color | storage |
+-------------------+---------+-----------+
| iPhone 15 | "Mavi" | "256GB" |
| SQL Kitabı | NULL | NULL | ← color yok
| Koşu Ayakkabısı | "Siyah/Beyaz" | NULL |
+-------------------+---------+-----------+Dikkat: Sonuçlar çift tırnak içinde — JSON string olarak döner. Tırnaksız değer için JSON_UNQUOTE veya ->>operatörü kullan.
Kısayol Operatörleri: -> ve ->>
-- -> : JSON_EXTRACT ile aynı (tırnaklı)
SELECT attributes->'$.color' AS color FROM products_v2;
-- Sonuç: "Mavi" (tırnaklı)
-- ->> : JSON_UNQUOTE(JSON_EXTRACT(...)) ile aynı (tırnaksız)
SELECT attributes->>'$.color' AS color FROM products_v2;
-- Sonuç: Mavi (tırnaksız)->> operatörü pratikte en çok kullanılan: tırnaksız, temiz değer döndürür. WHERE koşullarında ve karşılaştırmalarda bunu kullan.
İç İçe JSON ve Dizi Erişimi
-- JSON path syntax
SELECT
product_name,
attributes->>'$.color' AS color, -- Basit değer
attributes->'$.colors_available' AS all_colors, -- Dizi
attributes->>'$.colors_available[0]' AS first_color, -- Dizinin ilk elemanı
attributes->>'$.sizes[2]' AS third_size -- Dizinin 3. elemanı
FROM products_v2;+-------------------+--------+-----------------------------------+-------------+------------+
| product_name | color | all_colors | first_color | third_size |
+-------------------+--------+-----------------------------------+-------------+------------+
| iPhone 15 | Mavi | ["Mavi", "Siyah", "Pembe", "Sarı"] | Mavi | NULL |
| Koşu Ayakkabısı | Siyah/Beyaz | NULL | NULL | 40 |
+-------------------+--------+-----------------------------------+-------------+------------+JSON_OBJECT ve JSON_ARRAY — JSON Oluşturma
-- JSON nesnesi oluştur
SELECT JSON_OBJECT(
'name', first_name,
'email', email,
'city', city
) AS customer_json
FROM customers
LIMIT 3;+--------------------------------------------------------------+
| customer_json |
+--------------------------------------------------------------+
| {"name": "Ali", "email": "ali@test.com", "city": "İstanbul"} |
| {"name": "Zeynep", "email": "zeynep@test.com", "city": "Ankara"} |
+--------------------------------------------------------------+-- JSON dizisi oluştur
SELECT JSON_ARRAY(first_name, last_name, email) AS customer_array
FROM customers LIMIT 2;
-- ["Ali", "Yılmaz", "ali@test.com"]
-- ["Zeynep", "Demir", "zeynep@test.com"]
-- Sorgu sonucundan JSON dizisi oluştur
SELECT JSON_ARRAYAGG(product_name) AS product_list
FROM products
WHERE category_id = 1;
-- ["iPhone 15", "Samsung Galaxy", "MacBook Pro"]
-- JSON nesne dizisi oluştur
SELECT JSON_OBJECTAGG(product_name, price) AS price_map
FROM products
WHERE category_id = 1;
-- {"iPhone 15": 55000, "Samsung Galaxy": 28000, "MacBook Pro": 45000}JSON_SET, JSON_INSERT, JSON_REPLACE, JSON_REMOVE — JSON Değiştirme
-- JSON_SET: Varsa güncelle, yoksa ekle
UPDATE products_v2
SET attributes = JSON_SET(attributes,
'$.color', 'Kırmızı', -- Varsa güncelle
'$.warranty_months', 24 -- Yoksa ekle
)
WHERE product_id = 1;
-- JSON_INSERT: Sadece yoksa ekle (varsa değiştirme)
UPDATE products_v2
SET attributes = JSON_INSERT(attributes,
'$.color', 'Yeşil', -- color zaten var, değişmez
'$.weight', '187g' -- weight yok, eklenir
)
WHERE product_id = 1;
-- JSON_REPLACE: Sadece varsa güncelle (yoksa ekleme)
UPDATE products_v2
SET attributes = JSON_REPLACE(attributes,
'$.color', 'Siyah', -- color var, güncellenir
'$.processor', 'A17' -- processor yok, EKLENMez
)
WHERE product_id = 1;
-- JSON_REMOVE: Anahtarı sil
UPDATE products_v2
SET attributes = JSON_REMOVE(attributes, '$.5g', '$.ram')
WHERE product_id = 1;| Fonksiyon | Var olan anahtar | Olmayan anahtar |
|---|---|---|
| JSON_SET | Günceller | Ekler |
| JSON_INSERT | Değiştirmez | Ekler |
| JSON_REPLACE | Günceller | Eklemez |
| JSON_REMOVE | Siler | Bir şey yapmaz |
JSON_CONTAINS ve JSON_SEARCH — Arama
-- JSON_CONTAINS: Değer içeriyor mu?
SELECT product_name FROM products_v2
WHERE JSON_CONTAINS(attributes, '"Mavi"', '$.colors_available');
-- colors_available dizisinde "Mavi" var mı?
-- Sayısal değer kontrolü
SELECT product_name FROM products_v2
WHERE JSON_CONTAINS(attributes, '42', '$.sizes');
-- sizes dizisinde 42 var mı?
-- JSON_CONTAINS_PATH: Anahtar var mı?
SELECT product_name FROM products_v2
WHERE JSON_CONTAINS_PATH(attributes, 'one', '$.color', '$.brand');
-- 'one': en az birinin olması yeterli
-- 'all': hepsinin olması gerekir-- JSON_SEARCH: Değerin yolunu bul
SELECT JSON_SEARCH(attributes, 'one', 'Mavi') AS found_path
FROM products_v2
WHERE product_id = 1;
-- "$.colors_available[0]" — Mavi'nin JSON'daki yoluJSON_TABLE — JSON'ı İlişkisel Tabloya Dönüştürme (MySQL 8.0+)
JSON verilerini satırlara dönüştürmek için JSON_TABLE kullanılır. Bu, JSON dizilerini "açmak" (flatten) için çok güçlü bir araç:
-- JSON dizisini satırlara dönüştür
SELECT
p.product_name,
sizes.size_value
FROM products_v2 p,
JSON_TABLE(
p.attributes,
'$.sizes[*]' COLUMNS (
size_value INT PATH '$'
)
) AS sizes
WHERE p.product_id = 3;+-------------------+------------+
| product_name | size_value |
+-------------------+------------+
| Koşu Ayakkabısı | 38 |
| Koşu Ayakkabısı | 39 |
| Koşu Ayakkabısı | 40 |
| Koşu Ayakkabısı | 41 |
| Koşu Ayakkabısı | 42 |
| Koşu Ayakkabısı | 43 |
| Koşu Ayakkabısı | 44 |
+-------------------+------------+Karmaşık JSON_TABLE
-- Sipariş JSON'ı: iç içe yapıda
-- orders.metadata = '{"items": [{"product": "iPhone", "qty": 2}, ...]}'
SELECT
o.order_id,
items.product_name,
items.quantity,
items.price
FROM orders o,
JSON_TABLE(
o.metadata,
'$.items[*]' COLUMNS (
product_name VARCHAR(200) PATH '$.product',
quantity INT PATH '$.qty',
price DECIMAL(10,2) PATH '$.price' DEFAULT '0' ON EMPTY
)
) AS items;JSON Sütununda Index
JSON sütunlarına doğrudan index eklenemez ama generated column (türetilmiş sütun) ile çözülür:
-- Generated column + index
ALTER TABLE products_v2
ADD COLUMN color VARCHAR(50)
GENERATED ALWAYS AS (attributes->>'$.color') STORED;
CREATE INDEX idx_color ON products_v2(color);
-- Artık bu sorgu index kullanır:
SELECT * FROM products_v2 WHERE color = 'Mavi';MySQL 8.0.17+ sürümünde multi-valued index ile JSON dizilerinde de index kullanabilirsin:
-- JSON dizisi üzerinde index (MySQL 8.0.17+)
CREATE INDEX idx_sizes ON products_v2 ((CAST(attributes->'$.sizes' AS UNSIGNED ARRAY)));
-- Bu sorgu index kullanır:
SELECT * FROM products_v2
WHERE JSON_CONTAINS(attributes->'$.sizes', CAST(42 AS JSON));Gerçek Dünya Örnekleri
Örnek 1: Esnek Ürün Özellikleri
-- E-ticaret: ürün tiplerine göre değişen özellikler
INSERT INTO products_v2 (product_name, price, attributes) VALUES
('MacBook Pro', 45000, '{
"type": "laptop",
"brand": "Apple",
"specs": {
"cpu": "M3 Pro",
"ram": "18GB",
"storage": "512GB SSD",
"screen": "14.2 inch",
"battery": "17 saat"
},
"ports": ["USB-C", "HDMI", "SD Card", "MagSafe"],
"warranty_years": 2
}');
-- Nested değerlere erişim
SELECT
product_name,
attributes->>'$.specs.cpu' AS cpu,
attributes->>'$.specs.ram' AS ram,
attributes->>'$.specs.screen' AS screen,
JSON_LENGTH(attributes->'$.ports') AS port_count
FROM products_v2
WHERE attributes->>'$.type' = 'laptop';Örnek 2: Kullanıcı Tercihleri
CREATE TABLE user_preferences (
user_id INT PRIMARY KEY,
preferences JSON DEFAULT '{
"theme": "light",
"language": "tr",
"notifications": {
"email": true,
"push": true,
"sms": false
},
"items_per_page": 20
}'
);
-- Tercih güncelleme
UPDATE user_preferences
SET preferences = JSON_SET(preferences,
'$.theme', 'dark',
'$.notifications.sms', true
)
WHERE user_id = 101;
-- Tercih okuma
SELECT
user_id,
preferences->>'$.theme' AS theme,
preferences->>'$.notifications.email' AS email_notif,
preferences->'$.items_per_page' AS items_per_page
FROM user_preferences
WHERE user_id = 101;Örnek 3: Sipariş Meta Verileri
-- Siparişe ekstra bilgi ekleme
UPDATE orders
SET metadata = JSON_OBJECT(
'ip_address', '192.168.1.50',
'user_agent', 'Mozilla/5.0...',
'payment_method', 'credit_card',
'discount_code', 'YAZ2024',
'gift_wrapping', true,
'delivery_notes', 'Kapıda bırakınız'
)
WHERE order_id = 1001;
-- İndirim kodu kullanan siparişler
SELECT order_id, total_amount,
metadata->>'$.discount_code' AS discount
FROM orders
WHERE JSON_CONTAINS_PATH(metadata, 'one', '$.discount_code');JSON vs Ayrı Tablolar — Ne Zaman Hangisi?
✅ JSON Kullan
Yapısı değişken veriler (ürün özellikleri, kullanıcı tercihleri)
Nadiren sorgulanan meta veriler (log bilgileri, ekstra notlar)
Üçüncü parti API'den gelen yapısal veriler
Prototipleme aşamasında, şema henüz netleşmemişken
❌ JSON Kullanma
Sık sorgulanan, filtrelenen, JOIN'de kullanılan veriler
İlişkisel bütünlük (foreign key) gerektiren veriler
Raporlama ve aggregate hesaplamaların yapıldığı veriler
Büyük veri setlerinde performans kritik olduğunda
💡 İpucu: "Bu veriyi WHERE'de sık kullanacak mıyım?" sorusunun cevabı evet ise, ayrı sütun/tablo kullan. JSON, esneklik sağlar ama performans maliyeti vardır.
PostgreSQL Farklılıkları
-- PostgreSQL: jsonb tipi (binary, daha hızlı)
CREATE TABLE products (
id SERIAL PRIMARY KEY,
attributes JSONB -- JSON yerine JSONB kullan (performans)
);
-- PostgreSQL: GIN index (JSON dizileri ve nesneleri için)
CREATE INDEX idx_attrs ON products USING GIN (attributes);
-- MySQL'de bu kadar doğrudan index desteği yok
-- PostgreSQL: Daha zengin operatörler
SELECT * FROM products WHERE attributes @> '{"color": "Mavi"}'; -- İçeriyor mu?
SELECT * FROM products WHERE attributes ? 'color'; -- Anahtar var mı?
SELECT * FROM products WHERE attributes ->> 'price' > '1000'; -- Değer karşılaştırma
-- PostgreSQL: jsonb_agg, jsonb_build_object
SELECT jsonb_agg(jsonb_build_object('name', first_name, 'email', email))
FROM customers;PostgreSQL'in JSONB tipi, MySQL'in JSON tipinden daha performanslıdır çünkü binary formatta saklanır ve doğrudan GIN index destekler.
Özet
JSON veri tipi, esnek yapıdaki verileri ilişkisel veritabanında saklamayı sağlar
JSON_EXTRACT / ->>: JSON'dan değer çekme —
->>tırnaksız döndürürJSON_SET/INSERT/REPLACE/REMOVE: JSON'ı yerinde güncelleme
JSON_OBJECT/ARRAY: SQL'den JSON oluşturma
JSON_CONTAINS/SEARCH: JSON içinde arama
JSON_TABLE: JSON'ı satırlara dönüştürme (flatten)
JSON sütunlarına doğrudan index eklenemez — generated column ile çözülür
JSON esneklik sağlar ama performans maliyeti vardır — sık sorgulanan veriler için ayrı sütun tercih et
Sıkça Yapılan Hatalar
Her şeyi JSON'a koymak — JSON kolaylık sağlar ama ilişkisel modelin gücünü kaybedersin. customer_id, order_date gibi temel verileri JSON'da saklama.
`->` ve `->>` farkını bilmemek —
->tırnaklı JSON değer döndürür ("Mavi"),->>tırnaksız string döndürür (Mavi). WHERE karşılaştırmalarında->>kullan.JSON sütununda index olmadığını unutmak —
WHERE attributes->>'$.color' = 'Mavi'sorgusu full table scan yapar. Generated column + index ekle.JSON'ı validate etmemek — MySQL geçersiz JSON'ı engeller ama yapısal doğrulama (hangi anahtarlar olmalı?) uygulama katmanında yapılmalı.
Büyük JSON belgeleri saklamak — Bir satırda 10MB JSON performans felaketine yol açar. JSON'ı küçük ve odaklı tut, büyük verileri ayrı tablolara taşı.
AI Asistan
Sorularını yanıtlamaya hazır