← Kursa Dön
📄 Text · 30 min

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 text

JSON_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;
FonksiyonVar olan anahtarOlmayan anahtar
JSON_SETGüncellerEkler
JSON_INSERTDeğiştirmezEkler
JSON_REPLACEGüncellerEklemez
JSON_REMOVESilerBir ş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 yolu

JSON_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ür

  • JSON_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

  1. 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.

  2. `->` 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.

  3. JSON sütununda index olmadığını unutmakWHERE attributes->>'$.color' = 'Mavi' sorgusu full table scan yapar. Generated column + index ekle.

  4. JSON'ı validate etmemek — MySQL geçersiz JSON'ı engeller ama yapısal doğrulama (hangi anahtarlar olmalı?) uygulama katmanında yapılmalı.

  5. 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şı.