MariaDB ve MySQL’de CASE WHEN ile Koşullu Sorgu Yazma

Veritabanı sorgularında en güçlü araçlardan biri olan CASE WHEN ifadesi, SQL’de koşullu mantık kurmanın temel yoludur. Bir programlama dilindeki if-else yapısının SQL karşılığı olarak düşünebilirsiniz bunu. Raporlama sorgularında, veri dönüştürme işlemlerinde ve karmaşık iş mantığını tek bir sorguda ifade etmek istediğinizde CASE WHEN olmadan pek bir yere varamazsınız. Bu yazıda MariaDB ve MySQL üzerinde CASE WHEN kullanımını, gerçek dünya senaryolarıyla birlikte detaylıca ele alacağız.

CASE WHEN Nedir ve Neden Kullanılır?

CASE WHEN ifadesi, sorgu içinde satır bazında koşul değerlendirmesi yapmanızı sağlar. Sonuç sütunlarını dinamik olarak hesaplamak, verileri kategorize etmek veya belirli koşullara göre farklı değerler döndürmek için kullanılır.

İki temel kullanım şekli vardır:

Basit CASE (Simple CASE): Tek bir ifadeyi birden fazla değerle karşılaştırır.

Aranmış CASE (Searched CASE): Her WHEN dalında bağımsız bir koşul ifadesi değerlendirilir. Bu form çok daha esnektir ve gerçek dünya senaryolarında daha sık kullanılır.

Genel sözdizimi şöyledir:

-- Basit CASE
SELECT
    CASE kolon_adi
        WHEN 'deger1' THEN 'sonuc1'
        WHEN 'deger2' THEN 'sonuc2'
        ELSE 'varsayilan'
    END AS etiket
FROM tablo_adi;

-- Aranmış CASE
SELECT
    CASE
        WHEN kosul1 THEN 'sonuc1'
        WHEN kosul2 THEN 'sonuc2'
        ELSE 'varsayilan'
    END AS etiket
FROM tablo_adi;

ELSE kısmı zorunlu değildir. Eğer hiçbir koşul sağlanmazsa ve ELSE yoksa, ifade NULL döner. Bu davranışı aklınızın bir köşesinde tutun, çünkü beklenmedik NULL değerleri sorgu sonuçlarını bozabilir.

Örnek Veritabanı Hazırlama

Yazı boyunca kullanacağımız örnek tabloları oluşturalım. Bir e-ticaret sistemini simüle eden basit bir yapı kuracağız:

-- Veritabanı oluştur
CREATE DATABASE IF NOT EXISTS eticaret;
USE eticaret;

-- Müşteri tablosu
CREATE TABLE musteriler (
    id INT AUTO_INCREMENT PRIMARY KEY,
    ad VARCHAR(100),
    sehir VARCHAR(100),
    uyelik_tipi ENUM('standart', 'premium', 'vip'),
    kayit_tarihi DATE,
    toplam_harcama DECIMAL(10,2)
);

-- Sipariş tablosu
CREATE TABLE siparisler (
    id INT AUTO_INCREMENT PRIMARY KEY,
    musteri_id INT,
    urun_adi VARCHAR(200),
    miktar INT,
    birim_fiyat DECIMAL(10,2),
    siparis_tarihi DATETIME,
    durum VARCHAR(50),
    FOREIGN KEY (musteri_id) REFERENCES musteriler(id)
);

-- Örnek veri ekle
INSERT INTO musteriler (ad, sehir, uyelik_tipi, kayit_tarihi, toplam_harcama) VALUES
('Ahmet Yilmaz', 'Istanbul', 'premium', '2022-03-15', 4500.00),
('Fatma Kaya', 'Ankara', 'standart', '2023-01-10', 850.00),
('Mehmet Demir', 'Izmir', 'vip', '2021-06-20', 15200.00),
('Ayse Celik', 'Bursa', 'standart', '2023-07-05', 320.00),
('Can Arslan', 'Istanbul', 'premium', '2022-11-30', 6700.00);

INSERT INTO siparisler (musteri_id, urun_adi, miktar, birim_fiyat, siparis_tarihi, durum) VALUES
(1, 'Laptop', 1, 18000.00, '2024-01-15 10:30:00', 'teslim_edildi'),
(2, 'Klavye', 2, 350.00, '2024-01-18 14:20:00', 'beklemede'),
(3, 'Monitor', 2, 5500.00, '2024-01-20 09:15:00', 'kargoda'),
(4, 'Mouse', 1, 180.00, '2024-01-22 16:45:00', 'iptal'),
(5, 'Headset', 3, 800.00, '2024-01-25 11:00:00', 'teslim_edildi');

Temel CASE WHEN Kullanımı

Müşteri üyelik tiplerini Türkçe etiketlere çevirelim ve harcama durumuna göre müşteri segmentasyonu yapalım:

SELECT
    ad,
    sehir,
    CASE uyelik_tipi
        WHEN 'standart' THEN 'Standart Üye'
        WHEN 'premium' THEN 'Premium Üye'
        WHEN 'vip' THEN 'VIP Üye'
        ELSE 'Bilinmiyor'
    END AS uyelik_etiketi,
    CASE
        WHEN toplam_harcama < 1000 THEN 'Düşük'
        WHEN toplam_harcama BETWEEN 1000 AND 5000 THEN 'Orta'
        WHEN toplam_harcama BETWEEN 5001 AND 10000 THEN 'Yüksek'
        ELSE 'Çok Yüksek'
    END AS harcama_segmenti
FROM musteriler
ORDER BY toplam_harcama DESC;

Bu sorgu hem basit hem de aranmış CASE formunu bir arada kullanıyor. Gerçek projelerde ikisini aynı sorguda birden fazla kez kullanmak oldukça yaygın.

Sipariş Durumu Raporlama

Sipariş tablonuzda İngilizce durum değerleri tutup, kullanıcıya Türkçe göstermek istediğinizi düşünün. Ya da durum bazında renk kodu veya öncelik değeri atamak gerekebilir:

SELECT
    s.id AS siparis_no,
    m.ad AS musteri_adi,
    s.urun_adi,
    s.miktar * s.birim_fiyat AS toplam_tutar,
    CASE s.durum
        WHEN 'teslim_edildi' THEN 'Teslim Edildi'
        WHEN 'kargoda' THEN 'Kargoda'
        WHEN 'beklemede' THEN 'Onay Bekliyor'
        WHEN 'iptal' THEN 'İptal Edildi'
        ELSE 'Bilinmiyor'
    END AS durum_etiketi,
    CASE s.durum
        WHEN 'teslim_edildi' THEN 1
        WHEN 'kargoda' THEN 2
        WHEN 'beklemede' THEN 3
        WHEN 'iptal' THEN 4
        ELSE 5
    END AS durum_sirasi
FROM siparisler s
JOIN musteriler m ON s.musteri_id = m.id
ORDER BY durum_sirasi, s.siparis_tarihi DESC;

Burada durum_sirasi sütunu sayesinde sonuçları anlamlı bir sırada listeleyebiliyoruz. Teslim edilmiş siparişler önce, iptal edilenler en sona geliyor. Bu teknik, ORDER BY ile CASE WHEN kombinasyonu sıkça kullanılan bir pattern’dir.

CASE WHEN ile Koşullu Agregasyon

Bu, CASE WHEN‘in en güçlü kullanım senaryolarından biridir. Satırları gruplandırarak pivot benzeri raporlar üretebilirsiniz. Şehir bazında üyelik tiplerine göre müşteri sayısını tek bir sorguda çekelim:

SELECT
    sehir,
    COUNT(*) AS toplam_musteri,
    SUM(CASE WHEN uyelik_tipi = 'standart' THEN 1 ELSE 0 END) AS standart_sayi,
    SUM(CASE WHEN uyelik_tipi = 'premium' THEN 1 ELSE 0 END) AS premium_sayi,
    SUM(CASE WHEN uyelik_tipi = 'vip' THEN 1 ELSE 0 END) AS vip_sayi,
    ROUND(
        AVG(CASE WHEN uyelik_tipi = 'vip' THEN toplam_harcama END), 2
    ) AS vip_ort_harcama
FROM musteriler
GROUP BY sehir
ORDER BY toplam_musteri DESC;

SUM(CASE WHEN ... THEN 1 ELSE 0 END) kalıbı koşullu sayım için klasik bir tekniktir. AVG içinde CASE WHEN kullandığınızda ise ELSE yazmadığınızda eşleşmeyen satırlar NULL döner ve AVG hesaplamasında bu değerler zaten görmezden gelinir. Yani sadece VIP müşterilerin ortalaması otomatik olarak hesaplanır.

Tarih Bazlı Koşullu Sorgular

Zaman bazlı raporlama senaryoları çok yaygındır. Siparişleri hafta içi/hafta sonu ve gün saatine göre kategorize edelim:

SELECT
    id AS siparis_no,
    urun_adi,
    siparis_tarihi,
    CASE DAYOFWEEK(siparis_tarihi)
        WHEN 1 THEN 'Pazar'
        WHEN 2 THEN 'Pazartesi'
        WHEN 3 THEN 'Salı'
        WHEN 4 THEN 'Çarşamba'
        WHEN 5 THEN 'Perşembe'
        WHEN 6 THEN 'Cuma'
        WHEN 7 THEN 'Cumartesi'
    END AS siparis_gunu,
    CASE
        WHEN DAYOFWEEK(siparis_tarihi) IN (1, 7) THEN 'Hafta Sonu'
        ELSE 'Hafta İçi'
    END AS gun_tipi,
    CASE
        WHEN HOUR(siparis_tarihi) BETWEEN 6 AND 11 THEN 'Sabah'
        WHEN HOUR(siparis_tarihi) BETWEEN 12 AND 17 THEN 'Öğleden Sonra'
        WHEN HOUR(siparis_tarihi) BETWEEN 18 AND 22 THEN 'Akşam'
        ELSE 'Gece'
    END AS saat_dilimi
FROM siparisler
ORDER BY siparis_tarihi;

Bu tür sorgular özellikle BI araçlarıyla entegrasyon sırasında çok işe yarar. Grafana, Metabase veya Superset gibi araçlara veri beslerken zaman kategorileri önceden sorgu seviyesinde hazırlarsanız, frontend tarafında ekstra işlem yapmanıza gerek kalmaz.

Karmaşık İş Mantığı: İndirim Hesaplama

Gerçek bir e-ticaret sisteminde indirim hesaplama mantığı oldukça karmaşık olabilir. Hem üyelik tipine hem de sipariş tutarına göre dinamik indirim oranı belirleyelim:

SELECT
    m.ad AS musteri_adi,
    m.uyelik_tipi,
    s.urun_adi,
    s.miktar,
    s.birim_fiyat,
    s.miktar * s.birim_fiyat AS brut_tutar,
    CASE
        WHEN m.uyelik_tipi = 'vip' AND (s.miktar * s.birim_fiyat) > 10000 THEN 20
        WHEN m.uyelik_tipi = 'vip' THEN 15
        WHEN m.uyelik_tipi = 'premium' AND (s.miktar * s.birim_fiyat) > 5000 THEN 12
        WHEN m.uyelik_tipi = 'premium' THEN 8
        WHEN m.uyelik_tipi = 'standart' AND (s.miktar * s.birim_fiyat) > 3000 THEN 5
        ELSE 0
    END AS indirim_orani,
    ROUND(
        (s.miktar * s.birim_fiyat) *
        (1 - CASE
            WHEN m.uyelik_tipi = 'vip' AND (s.miktar * s.birim_fiyat) > 10000 THEN 0.20
            WHEN m.uyelik_tipi = 'vip' THEN 0.15
            WHEN m.uyelik_tipi = 'premium' AND (s.miktar * s.birim_fiyat) > 5000 THEN 0.12
            WHEN m.uyelik_tipi = 'premium' THEN 0.08
            WHEN m.uyelik_tipi = 'standart' AND (s.miktar * s.birim_fiyat) > 3000 THEN 0.05
            ELSE 0
        END), 2
    ) AS net_tutar
FROM siparisler s
JOIN musteriler m ON s.musteri_id = m.id
WHERE s.durum != 'iptal';

Bu sorguda CASE WHEN ifadesinin iki kez kullanıldığını fark ettiniz mi? Hem indirim oranını görsel olarak göstermek hem de net tutarı hesaplamak için aynı mantığı tekrarlamak zorunda kaldık. Bunu temizlemenin yolu WITH (CTE) kullanmaktır:

WITH siparis_ozet AS (
    SELECT
        m.ad AS musteri_adi,
        m.uyelik_tipi,
        s.urun_adi,
        s.miktar * s.birim_fiyat AS brut_tutar,
        CASE
            WHEN m.uyelik_tipi = 'vip' AND (s.miktar * s.birim_fiyat) > 10000 THEN 0.20
            WHEN m.uyelik_tipi = 'vip' THEN 0.15
            WHEN m.uyelik_tipi = 'premium' AND (s.miktar * s.birim_fiyat) > 5000 THEN 0.12
            WHEN m.uyelik_tipi = 'premium' THEN 0.08
            WHEN m.uyelik_tipi = 'standart' AND (s.miktar * s.birim_fiyat) > 3000 THEN 0.05
            ELSE 0
        END AS indirim_orani
    FROM siparisler s
    JOIN musteriler m ON s.musteri_id = m.id
    WHERE s.durum != 'iptal'
)
SELECT
    musteri_adi,
    uyelik_tipi,
    urun_adi,
    brut_tutar,
    indirim_orani * 100 AS indirim_yuzdesi,
    ROUND(brut_tutar * (1 - indirim_orani), 2) AS net_tutar
FROM siparis_ozet
ORDER BY brut_tutar DESC;

CTE ile mantığı bir kez yazıp sonucu tekrar kullanmak hem okunabilirliği artırır hem de bakım maliyetini düşürür.

CASE WHEN ile UPDATE Sorgularında Toplu Güncelleme

CASE WHEN sadece SELECT sorgularında değil, UPDATE ifadelerinde de kullanılabilir. Bu özellik, birden fazla güncelleme sorgusunu tek bir işlemde birleştirmenizi sağlar:

-- Harcama miktarına göre üyelik tipini toplu güncelle
UPDATE musteriler
SET uyelik_tipi = CASE
    WHEN toplam_harcama >= 10000 THEN 'vip'
    WHEN toplam_harcama >= 3000 THEN 'premium'
    ELSE 'standart'
END
WHERE kayit_tarihi < '2024-01-01';

-- Sonucu doğrula
SELECT ad, toplam_harcama, uyelik_tipi
FROM musteriler
ORDER BY toplam_harcama DESC;

Bu yaklaşım, ayrı ayrı üç UPDATE sorgusu yazmak yerine tek bir geçişte işi bitiriyor. Büyük tablolarda bu fark ciddi performans avantajı sağlar çünkü tabloyu bir kez taramanız yeterli olur.

CASE WHEN ile ORDER BY Özelleştirme

Sıralama mantığınız doğrudan bir sütuna dayanmıyorsa, ORDER BY içinde CASE WHEN kullanabilirsiniz. Sipariş durumlarını iş önceliğine göre sıralayalım:

SELECT
    s.id,
    m.ad AS musteri_adi,
    s.urun_adi,
    s.durum,
    s.siparis_tarihi
FROM siparisler s
JOIN musteriler m ON s.musteri_id = m.id
ORDER BY
    CASE s.durum
        WHEN 'beklemede' THEN 1
        WHEN 'kargoda' THEN 2
        WHEN 'teslim_edildi' THEN 3
        WHEN 'iptal' THEN 4
        ELSE 5
    END ASC,
    s.siparis_tarihi ASC;

Bu teknik, enum sıralamasının alfabetik veya ekleme sırasına göre değil, iş mantığına göre yapılması gerektiği durumlarda çok değerlidir.

Performans ve Dikkat Edilmesi Gerekenler

CASE WHEN kullanırken aklınızda bulundurmanız gereken birkaç önemli nokta var:

WHEN koşullarının sırası önemlidir: SQL, ilk eşleşen WHEN dalında durur ve sonraki koşulları değerlendirmez. En özel ve nadir durumları üste, genel durumları alta yazın.

NULL değerlere dikkat edin: CASE WHEN kolon = 'deger' ifadesinde eğer kolon değeri NULL ise hiçbir WHEN dalı eşleşmez. NULL kontrolü için WHEN kolon IS NULL THEN ... şeklinde açıkça belirtmeniz gerekir.

İndeks kullanımına dikkat edin: WHERE koşulunda CASE WHEN kullanmak genellikle indeks kullanımını engeller. Bu durumu minimize etmek için mümkünse koşulları WHERE dışında tutun.

Veri tipi tutarlılığı: CASE WHEN ifadesi tüm THEN dallarının aynı veri tipinden değer döndürmesini bekler. Karışık tipler varsa MariaDB/MySQL otomatik dönüşüm yapar ama bu beklenmedik sonuçlara yol açabilir. Örneğin:

-- Problemli: karışık tip
SELECT CASE WHEN 1=1 THEN 'metin' ELSE 42 END;
-- Sonuç: 'metin' (sayı metne dönüştürüldü)

-- Doğru yaklaşım: tutarlı tip kullan
SELECT CASE WHEN 1=1 THEN 'metin' ELSE '42' END;
SELECT CASE WHEN 1=1 THEN CAST(1 AS DECIMAL) ELSE 42.0 END;

İç içe CASE kullanımı: Mümkün olduğunca iç içe CASE yazmaktan kaçının. Okunabilirliği ciddi ölçüde düşürür. Bunun yerine CTE veya subquery kullanmayı tercih edin.

Yaygın Hatalar ve Çözümleri

Hata 1: ELSE unutmak ve beklenmedik NULL değerler almak.

-- Hatalı: ELSE yok, bazi satirlar NULL donebilir
SELECT CASE WHEN puan > 80 THEN 'İyi' WHEN puan > 60 THEN 'Orta' END FROM notlar;

-- Dogru
SELECT CASE WHEN puan > 80 THEN 'İyi' WHEN puan > 60 THEN 'Orta' ELSE 'Düşük' END FROM notlar;

Hata 2: Koşul sırasını yanlış belirlemek.

-- Hatalı: puan=90 icin ilk kosul zaten saglandigi icin 'Orta' donecek
SELECT CASE
    WHEN puan > 50 THEN 'Orta'
    WHEN puan > 80 THEN 'İyi'  -- Bu satira hic ulasilmaz
END FROM notlar;

-- Dogru: en kısıtlayıcı koşul önce
SELECT CASE
    WHEN puan > 80 THEN 'İyi'
    WHEN puan > 50 THEN 'Orta'
    ELSE 'Düşük'
END FROM notlar;

Sonuç

CASE WHEN ifadesi, SQL’in en pratik ve güçlü araçlarından biridir. Veri katmanında koşullu mantık kurmak, uygulama katmanında ekstra işlem yapmak yerine çok daha verimli bir yaklaşımdır. Veritabanından ham veriyi çekip uygulama seviyesinde sınıflandırma yapmak yerine, bu sınıflandırmayı doğrudan sorguda yapmak hem ağ trafiğini azaltır hem de kod karmaşıklığını düşürür.

Yazı boyunca gördüğümüz senaryoları özetlemek gerekirse: basit etiket dönüşümleri, koşullu agregasyon ile pivot raporlama, dinamik indirim hesaplama, toplu UPDATE işlemleri ve özel sıralama mantığı bunların başında geliyor. Bu kullanım kalıplarını özümsediğinizde, daha önce kod tarafında çözdüğünüz pek çok problemi veritabanı katmanında çok daha temiz bir şekilde halledebileceğinizi göreceksiniz.

CTE ile kombinasyonu ise asıl gücü ortaya çıkarıyor. Karmaşık CASE WHEN mantığını bir kez yazıp sonraki sorgu adımlarında sütun adıyla kullanmak, hem bakım kolaylığı hem de okunabilirlik açısından büyük fark yaratıyor. Bir sonraki raporlama sorgusunu yazarken CASE WHEN‘e başvurmayı ihmal etmeyin.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir