MariaDB ve MySQL’de COALESCE ile NULL Değer Yönetimi
Veritabanı yönetiminde en sık karşılaşılan sorunların başında NULL değerler gelir. Bir kullanıcı telefon numarasını girmemiş, bir sipariş henüz teslim edilmemiş, bir çalışanın ikinci adı yok… Bu durumların hepsinde veritabanında NULL ile karşılaşırsınız. Ve NULL değerler, sorgularınızda beklenmedik sonuçlar doğurabilir, raporlarınızı bozabilir, uygulamanızın çökmesine neden olabilir. İşte tam bu noktada COALESCE fonksiyonu devreye girer. MariaDB ve MySQL’de günlük hayatın vazgeçilmez araçlarından biri olan COALESCE’i bu yazıda her yönüyle ele alacağız.
COALESCE Nedir ve Neden Kullanılır?
COALESCE, kendisine verilen argüman listesinde soldan sağa doğru ilerleyerek ilk NULL olmayan değeri döndüren bir SQL fonksiyonudur. Eğer tüm argümanlar NULL ise sonuç da NULL döner.
Temel sözdizimi şöyledir:
COALESCE(değer1, değer2, değer3, ...)
Basit bir örnekle başlayalım:
SELECT COALESCE(NULL, NULL, 'üçüncü değer', 'dördüncü değer');
-- Sonuç: üçüncü değer
SELECT COALESCE(NULL, 42, NULL, 100);
-- Sonuç: 42
SELECT COALESCE(NULL, NULL, NULL);
-- Sonuç: NULL
Bu fonksiyon ANSI SQL standardının bir parçasıdır, yani MySQL ve MariaDB dışında PostgreSQL, SQL Server, Oracle gibi sistemlerde de aynı şekilde çalışır. Bu da onu taşınabilir bir araç yapar.
Peki IFNULL ile farkı ne? IFNULL sadece iki argüman alır ve MySQL/MariaDB’ye özgüdür. COALESCE ise birden fazla argüman alabilir ve standarttır. Prodüksiyonda her zaman COALESCE kullanmanızı öneririm çünkü hem daha esnek hem de başka bir veritabanına geçiş senaryosunda daha az baş ağrısı yaratır.
Gerçek Dünya Senaryosu 1: Kullanıcı Profil Bilgileri
Diyelim ki bir e-ticaret platformu yönetiyorsunuz. Kullanıcılar kayıt olurken bazı bilgileri zorunlu doldururken bazılarını boş bırakabiliyor. users tablonuz şöyle bir yapıya sahip:
- id: Kullanıcı ID
- username: Kullanıcı adı (zorunlu)
- display_name: Görünen ad (opsiyonel)
- first_name: Ad (opsiyonel)
- phone: Telefon (opsiyonel)
- mobile: Cep telefonu (opsiyonel)
- bio: Biyografi (opsiyonel)
Kullanıcıya bir karşılama mesajı göstermek istiyorsunuz. Öncelik sırası şöyle: önce display_name, yoksa first_name, o da yoksa username kullansın.
SELECT
id,
COALESCE(display_name, first_name, username) AS gosterilen_ad,
COALESCE(mobile, phone, 'Telefon bilgisi yok') AS iletisim_no,
COALESCE(bio, 'Henüz bir biyografi eklenmemiş.') AS kullanici_bio
FROM users
WHERE id = 1042;
Bu sorgu sayesinde uygulama katmanında onlarca if kontrolü yazmak yerine işi doğrudan veritabanına yaptırıyorsunuz. Bu hem performans kazancı sağlar hem de kod karmaşıklığını azaltır.
Gerçek Dünya Senaryosu 2: Sipariş Yönetim Sistemi
Bir lojistik şirketinin veritabanını yönetiyorsunuz. orders tablosunda siparişlerin çeşitli tarih bilgileri tutuluyor:
- order_date: Sipariş tarihi
- processing_date: İşleme alındığı tarih
- shipping_date: Kargoya verildiği tarih
- delivery_date: Teslim tarihi
- cancelled_date: İptal tarihi
Bir sipariş için “en son işlem tarihi” bilgisini çekmek istiyorsunuz. Yani hangisi en son dolmuşsa onu görmek istiyorsunuz:
SELECT
order_id,
order_date,
COALESCE(
cancelled_date,
delivery_date,
shipping_date,
processing_date,
order_date
) AS son_islem_tarihi,
CASE
WHEN cancelled_date IS NOT NULL THEN 'İptal Edildi'
WHEN delivery_date IS NOT NULL THEN 'Teslim Edildi'
WHEN shipping_date IS NOT NULL THEN 'Kargoda'
WHEN processing_date IS NOT NULL THEN 'İşlemde'
ELSE 'Beklemede'
END AS siparis_durumu
FROM orders
WHERE order_date >= '2024-01-01'
ORDER BY son_islem_tarihi DESC;
Burada COALESCE ile CASE yapısını birlikte kullandık. COALESCE tarih için, CASE ise durum etiketi için çalışıyor. Bu ikili kombinasyon çok güçlü sonuçlar üretir.
COALESCE ile Matematiksel İşlemler
NULL değerlerin en sinir bozucu özelliği, herhangi bir matematiksel işlemde NULL ile karşılaşınca sonucun otomatik olarak NULL olmasıdır. Örneğin:
SELECT 100 + NULL;
-- Sonuç: NULL
SELECT NULL * 5;
-- Sonuç: NULL
Bu durum özellikle raporlama sorgularında ciddi sorunlara yol açar. Bir maaş hesaplama senaryosu düşünelim:
-- Problemli sorgu: bonus NULL olunca toplam maaş da NULL döner
SELECT
employee_id,
base_salary + bonus AS toplam_maas -- YANLIS!
FROM employees;
-- Doğru sorgu: COALESCE ile NULL'ları sıfıra çeviriyoruz
SELECT
employee_id,
base_salary + COALESCE(bonus, 0) AS toplam_maas,
base_salary + COALESCE(bonus, 0) + COALESCE(overtime_pay, 0) AS tam_maas,
COALESCE(tax_rate, 0.20) AS vergi_orani,
(base_salary + COALESCE(bonus, 0)) * (1 - COALESCE(tax_rate, 0.20)) AS net_maas
FROM employees
ORDER BY net_maas DESC;
Prodüksiyonda bu hatayı çok görüyorum. Geliştirici test ortamında tüm alanları dolu verilerle test ediyor, her şey güzel çalışıyor. Ama prodüksiyona geçince bazı çalışanların bonusu girilmemiş olduğu için raporlar yanlış çıkıyor. COALESCE bu tür sürprizleri önlemenin en güvenilir yoludur.
GROUP BY ve Agregasyon Sorgularında COALESCE
Raporlama sorgularında COALESCE’in asıl gücü ortaya çıkar. Bir satış raporu hazırlıyorsunuz ve bazı kategorilerin satışı henüz girilmemiş:
SELECT
p.category_id,
c.category_name,
COUNT(s.sale_id) AS satis_adedi,
COALESCE(SUM(s.amount), 0) AS toplam_satis,
COALESCE(AVG(s.amount), 0) AS ortalama_satis,
COALESCE(MAX(s.amount), 0) AS en_yuksek_satis,
COALESCE(MIN(s.amount), 0) AS en_dusuk_satis
FROM categories c
LEFT JOIN products p ON c.id = p.category_id
LEFT JOIN sales s ON p.id = s.product_id
AND s.sale_date BETWEEN '2024-01-01' AND '2024-12-31'
GROUP BY p.category_id, c.category_name
ORDER BY toplam_satis DESC;
Burada LEFT JOIN kullandığımız için bazı kategoriler için satış kaydı hiç olmayabilir. Bu durumda SUM, AVG, MAX, MIN fonksiyonları NULL döndürür. COALESCE ile bunları sıfıra çevirerek raporun düzgün görünmesini sağlıyoruz.
Önemli not: COUNT fonksiyonu NULL satırları saymaz ama hiç eşleşme yoksa 0 döndürür, bu yüzden COUNT için COALESCE gerekmez. Ama SUM, AVG, MAX, MIN için durum farklıdır, bu fonksiyonlar hiç satır yoksa NULL döndürür.
COALESCE ile UPDATE İşlemleri
Toplu güncelleme senaryolarında COALESCE çok işe yarar. Diyelim ki harici bir kaynaktan gelen verilerle veritabanınızı güncelliyorsunuz, ancak gelen veride bazı alanlar boş (NULL) olduğunda mevcut değeri korumak istiyorsunuz:
-- Senaryo: Harici sistem customer_updates tablosuna yeni verileri aktardı
-- Ama bazı alanlar NULL. Mevcut değerleri koruyarak sadece doldurulanları güncelle.
UPDATE customers c
JOIN customer_updates u ON c.id = u.customer_id
SET
c.email = COALESCE(u.new_email, c.email),
c.phone = COALESCE(u.new_phone, c.phone),
c.address = COALESCE(u.new_address, c.address),
c.credit_limit = COALESCE(u.new_credit_limit, c.credit_limit),
c.updated_at = NOW()
WHERE u.import_date = CURDATE();
Bu sorgu olmadan, güncelleme yaparken önce mevcut değerleri okuyup NULL kontrolü yapmanız gerekirdi. COALESCE ile tek sorguda hem NULL kontrolü hem de güncelleme yapılıyor. Veri entegrasyon projelerinde bu pattern’i çok sık kullanıyorum.
COALESCE ile Dinamik Sütun Birleştirme
Kullanıcı arayüzü için tam ad oluşturma gibi senaryolar da çok yaygındır. Türkçe isim yapısını düşünürsek, bazı kişilerin ikinci adı var, bazılarında yok:
SELECT
id,
-- Boşluk karakterini de akıllıca birleştiriyoruz
TRIM(
CONCAT(
COALESCE(first_name, ''),
CASE WHEN middle_name IS NOT NULL THEN CONCAT(' ', middle_name) ELSE '' END,
CASE WHEN last_name IS NOT NULL THEN CONCAT(' ', last_name) ELSE '' END
)
) AS tam_ad,
-- Alternatif: CONCAT_WS ile daha temiz
CONCAT_WS(' ',
COALESCE(first_name, ''),
middle_name, -- NULL olanları CONCAT_WS zaten atlar
last_name
) AS tam_ad_v2,
-- Adres satırı oluşturma
CONCAT_WS(', ',
address_line1,
COALESCE(address_line2, NULL), -- NULL ise CONCAT_WS atlar
district,
city,
COALESCE(postal_code, '')
) AS tam_adres
FROM customers;
Burada CONCAT_WS ve COALESCE‘in birlikte kullanımına dikkat edin. CONCAT_WS NULL değerleri zaten atlar ama boş string’leri atlamaz. Bu yüzden bazen ikisini birlikte kullanmak gerekir.
Subquery ve JOIN’lerde COALESCE
İleri seviye bir senaryo: Her müşteri için son sipariş bilgisini getirin, sipariş vermeyenlere varsayılan değer atayın.
SELECT
c.id AS musteri_id,
c.username,
COALESCE(son_siparis.order_date, 'Hiç sipariş vermedi') AS son_siparis_tarihi,
COALESCE(son_siparis.total_amount, 0) AS son_siparis_tutari,
COALESCE(son_siparis.status, 'Sipariş yok') AS son_siparis_durumu,
COALESCE(toplam.siparis_sayisi, 0) AS toplam_siparis_sayisi,
COALESCE(toplam.toplam_harcama, 0) AS toplam_harcama
FROM customers c
-- En son sipariş için subquery
LEFT JOIN (
SELECT
customer_id,
order_date,
total_amount,
status,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) AS rn
FROM orders
) son_siparis ON c.id = son_siparis.customer_id AND son_siparis.rn = 1
-- Toplam istatistikler için subquery
LEFT JOIN (
SELECT
customer_id,
COUNT(*) AS siparis_sayisi,
SUM(total_amount) AS toplam_harcama
FROM orders
GROUP BY customer_id
) toplam ON c.id = toplam.customer_id
ORDER BY toplam_harcama DESC;
Bu sorgu müşteri segmentasyonu raporları için birebir. Hiç sipariş vermemiş müşteriler de listede görünür ama değerleri anlamlı varsayılanlarla doldurulmuş olur.
COALESCE ile Stored Procedure
Prodüksiyon ortamında stored procedure içinde COALESCE kullanımı da çok yaygın. Özellikle esnek arama fonksiyonları yazarken:
DELIMITER //
CREATE PROCEDURE AraUrunler(
IN p_kategori_id INT,
IN p_min_fiyat DECIMAL(10,2),
IN p_max_fiyat DECIMAL(10,2),
IN p_marka VARCHAR(100),
IN p_stok_durumu VARCHAR(20)
)
BEGIN
SELECT
p.id,
p.product_name,
p.price,
p.stock_quantity,
COALESCE(p.discount_price, p.price) AS satis_fiyati,
COALESCE(b.brand_name, 'Markasız') AS marka,
COALESCE(p.description, 'Açıklama henüz girilmemiş') AS aciklama,
CASE
WHEN p.stock_quantity = 0 THEN 'Stokta yok'
WHEN p.stock_quantity < 10 THEN 'Az kaldı'
ELSE 'Stokta var'
END AS stok_durumu
FROM products p
LEFT JOIN brands b ON p.brand_id = b.id
WHERE
(p_kategori_id IS NULL OR p.category_id = p_kategori_id)
AND p.price >= COALESCE(p_min_fiyat, 0)
AND p.price <= COALESCE(p_max_fiyat, 999999999)
AND (p_marka IS NULL OR b.brand_name = p_marka)
AND (
p_stok_durumu IS NULL
OR (p_stok_durumu = 'var' AND p.stock_quantity > 0)
OR (p_stok_durumu = 'yok' AND p.stock_quantity = 0)
)
ORDER BY
COALESCE(p.discount_price, p.price) ASC;
END //
DELIMITER ;
-- Kullanım örnekleri:
CALL AraUrunler(NULL, NULL, NULL, NULL, NULL); -- Tüm ürünler
CALL AraUrunler(5, 100.00, 500.00, NULL, 'var'); -- Filtreli arama
CALL AraUrunler(NULL, 50.00, NULL, 'Samsung', NULL); -- Marka filtresi
WHERE koşulunda p.price >= COALESCE(p_min_fiyat, 0) kullanımına dikkat edin. Eğer minimum fiyat parametresi geçilmezse (NULL gelirse) 0 kullanılıyor, yani alt limit ortadan kalkıyor. Bu şekilde tek bir stored procedure ile esnek filtreleme yapabiliyorsunuz.
Performans Notları
COALESCE kullanırken dikkat edilmesi gereken bazı performans konuları var:
Index kullanımı: WHERE koşulunda COALESCE kullanmak bazen index’in devre dışı kalmasına neden olabilir. Şöyle bir durumdan kaçının:
-- Kötü: Index kullanamayabilir
SELECT * FROM orders WHERE COALESCE(delivery_date, order_date) > '2024-06-01';
-- İyi: Her koşul ayrı ayrı yazılır
SELECT * FROM orders
WHERE delivery_date > '2024-06-01'
OR (delivery_date IS NULL AND order_date > '2024-06-01');
Subquery içinde COALESCE: Eğer COALESCE içine pahalı bir subquery koyuyorsanız dikkatli olun:
-- Dikkatli kullanın: Her satır için subquery çalışır
SELECT
p.id,
COALESCE(
(SELECT AVG(r.rating) FROM reviews r WHERE r.product_id = p.id),
0
) AS ortalama_puan
FROM products p;
-- Daha verimli: JOIN ile çözün
SELECT
p.id,
COALESCE(r.avg_rating, 0) AS ortalama_puan
FROM products p
LEFT JOIN (
SELECT product_id, AVG(rating) AS avg_rating
FROM reviews
GROUP BY product_id
) r ON p.id = r.product_id;
IFNULL, NULLIF ve COALESCE Karşılaştırması
Bazen hangisini kullanacağınızı şaşırabilirsiniz, kısaca özetleyeyim:
COALESCE(a, b, c…):
- Birden fazla argüman alır
- ANSI SQL standardı, taşınabilir
- En esnek seçenek
- Kullanım:
COALESCE(telefon, mobil, 'Yok')
IFNULL(a, b):
- Sadece iki argüman alır
- MySQL/MariaDB’ye özgü
- Basit durumlar için yeterli
- Kullanım:
IFNULL(telefon, 'Belirtilmemiş')
NULLIF(a, b):
- İki değer eşitse NULL döndürür, değilse birinci değeri döndürür
- COALESCE’in tersi gibi düşünülebilir
- Sıfıra bölme hatalarını önlemek için çok kullanılır
- Kullanım:
100 / NULLIF(toplam, 0)– toplam sıfırsa NULL döner, sıfıra bölme hatası vermez
Bu üçünü birlikte kullanmak istediğinizde şöyle bir örnek verebilirim:
SELECT
product_id,
-- Satılan ürün yoksa NULL, varsa yüzde hesapla
COALESCE(
sold_count / NULLIF(total_stock, 0) * 100,
0
) AS satis_yuzdesi,
IFNULL(last_sale_date, '2000-01-01') AS son_satis_tarihi
FROM product_stats;
Sık Yapılan Hatalar
Yıllar içinde gördüğüm yaygın hatalar:
- Tip uyumsuzluğu: COALESCE tüm argümanların aynı tipte olmasını bekler ya da örtük dönüşüm yapar.
COALESCE(sayi_sutunu, 'yok')gibi bir kullanımda tip hatası alabilirsiniz.COALESCE(sayi_sutunu, 0)veyaCOALESCE(CAST(sayi_sutunu AS CHAR), 'yok')kullanın.
- Boş string ile NULL karıştırma:
''(boş string) ile NULL aynı şey değildir. Eğer hem NULL’ları hem boş stringleri yakalamak istiyorsanız:COALESCE(NULLIF(TRIM(sutun), ''), 'Varsayılan')şeklinde yazın.
- Performanssız WHERE kullanımı: Yukarıda bahsettiğim index sorununu unutmayın.
- Gereksiz iç içe COALESCE:
COALESCE(COALESCE(a, b), c)yerine direktCOALESCE(a, b, c)yazın.
Sonuç
COALESCE, MariaDB ve MySQL’de NULL değer yönetiminin temel taşıdır. Basit bir kullanıcı arayüzü sorgusundan karmaşık raporlama işlemlerine, veri entegrasyonundan stored procedure’lere kadar her yerde karşınıza çıkar.
Özetlemek gerekirse, COALESCE size şunları sağlar:
- Uygulama katmanındaki yükü azaltır: NULL kontrollerini veritabanında yaparak kod karmaşıklığını düşürür
- Tutarlı veri sunar: Rapor ve listelerde NULL yerine anlamlı varsayılan değerler gösterir
- Matematiksel hataları önler: NULL içeren hesaplamalar sonucu bozmaz
- Taşınabilir kod üretir: ANSI standardı olduğu için farklı veritabanlarına geçişte sorun yaşamazsınız
- Toplu işlemleri kolaylaştırır: UPDATE ve INSERT sorgularında akıllı varsayılan mantığı kurar
Pratik önerim: Bir tablo tasarlarken her nullable sütun için “Bu sütun NULL olduğunda ne göstereceğim?” sorusunu sorgulamalarınıza yansıtın. COALESCE’i erken ve doğru yerlerde kullanmak, ilerleyen süreçte çok fazla bug’ı ve veri tutarsızlığını önler. NULL değerler kaçınılmazdır, ama onları yönetmek sizin elinizde.
