NULL Değerleri Kontrol Etmek için IS NULL Kullanımı
Veritabanı yönetiminde en sık karşılaşılan sorunlardan biri, tablolarda eksik veya tanımsız değerlerin nasıl ele alınacağıdır. NULL, bir alanın değer içermediğini, yani “bilinmiyor” veya “tanımsız” olduğunu ifade eder. Bu durum, sıradan karşılaştırma operatörlerinin işe yaramadığı özel bir durumdur. WHERE kolon = NULL yazdığınızda hiçbir sonuç dönmez, çünkü NULL bir değer değil, değerin yokluğudur. İşte tam bu yüzden MariaDB ve MySQL’de IS NULL operatörü devreye girer.
NULL Nedir ve Neden Özeldir?
NULL kavramını anlamadan IS NULL kullanımını doğru kavramak mümkün değildir. NULL, matematiksel olarak tanımsız bir durumdur. Sıfırdan farklıdır, boş string’den farklıdır, tamamen farklı bir kavramdır.
NULL’ın temel özellikleri:
- NULL hiçbir değerle eşit değildir, kendisiyle bile eşit değildir
NULL = NULLifadesiTRUEdöndürmez,NULLdöndürür- Aritmetik işlemlerde NULL içeren her ifade NULL sonuç verir
NULL + 5,NULL * 0,NULL || 'metin'hepsi NULL döner
Bu yüzden WHERE kolon = NULL yerine WHERE kolon IS NULL kullanmanız zorunludur. Yeni başlayan geliştiricilerin ve bazen deneyimli sysadminlerin bile düştüğü bu tuzak, saatlerce süren debug süreçlerine yol açabilir.
IS NULL Temel Sözdizimi
Temel kullanım son derece basittir. Herhangi bir SELECT, UPDATE, DELETE veya WHERE ifadesinde kullanabilirsiniz.
-- Temel IS NULL kullanımı
SELECT * FROM tablo_adi WHERE kolon_adi IS NULL;
-- IS NOT NULL kullanımı
SELECT * FROM tablo_adi WHERE kolon_adi IS NOT NULL;
-- Birden fazla koşulla birlikte
SELECT * FROM tablo_adi
WHERE kolon1 IS NULL
AND kolon2 IS NOT NULL;
Şimdi gerçek dünya senaryolarına geçelim. Teorik bilgi önemli ama asıl mesele bunları production ortamında nasıl uygulayacağınızdır.
Senaryo 1: E-Ticaret Sisteminde Tamamlanmamış Siparişler
Bir e-ticaret platformu yönetiyorsunuz. Siparişler tablosunda kargo_tarihi alanı, sipariş henüz gönderilmemişse NULL kalıyor. Müşteri hizmetleri ekibi, hangi siparişlerin hâlâ depoda beklediğini görmek istiyor.
-- Kargo tarihi girilmemiş, yani henüz gönderilmemiş siparişler
SELECT
siparis_id,
musteri_adi,
siparis_tarihi,
toplam_tutar,
durum
FROM siparisler
WHERE kargo_tarihi IS NULL
AND durum != 'iptal'
ORDER BY siparis_tarihi ASC;
Bu sorgu size bekleyen tüm siparişleri verir. Şimdi bunu biraz daha geliştirelim. Diyelim ki 3 günden fazla süredir bekleyen siparişleri bulmak istiyorsunuz:
-- 3 günden fazla süredir kargoya verilmemiş siparişler
SELECT
siparis_id,
musteri_adi,
musteri_email,
siparis_tarihi,
DATEDIFF(NOW(), siparis_tarihi) AS bekleme_gun_sayisi,
toplam_tutar
FROM siparisler
WHERE kargo_tarihi IS NULL
AND durum = 'onaylandi'
AND siparis_tarihi < DATE_SUB(NOW(), INTERVAL 3 DAY)
ORDER BY bekleme_gun_sayisi DESC;
Bu tür sorgular, operasyon ekiplerinin günlük rutinlerinde son derece kritiktir. Cron job ile çalıştırıp sonuçları mail olarak gönderebilirsiniz.
Senaryo 2: Kullanıcı Profil Eksikliklerini Tespit Etmek
Bir SaaS uygulaması yönetiyorsunuz. Kullanıcıların profil bilgilerini tamamlaması gerekiyor ama bir kısmı kayıt olduktan sonra profil sayfasını hiç ziyaret etmemiş. Telefon numarası veya şirket adı girilmemiş kullanıcıları tespit etmeniz gerekiyor.
-- Profil bilgilerini tamamlamamış kullanıcılar
SELECT
kullanici_id,
email,
kayit_tarihi,
CASE
WHEN telefon IS NULL AND sirket_adi IS NULL THEN 'Telefon ve Şirket eksik'
WHEN telefon IS NULL THEN 'Telefon eksik'
WHEN sirket_adi IS NULL THEN 'Şirket adı eksik'
END AS eksik_bilgi
FROM kullanicilar
WHERE telefon IS NULL
OR sirket_adi IS NULL
AND aktif = 1
ORDER BY kayit_tarihi DESC;
Bu sorguyu CASE ifadesiyle birleştirdiğinizde hangi alanların eksik olduğunu da kategorize edebilirsiniz. Pazarlama ekibi bu listeyi kullanarak hedefli e-posta kampanyaları düzenleyebilir.
Senaryo 3: JOIN Sorgularında IS NULL ile Anti-Join
Bu, ileri düzey ama son derece güçlü bir tekniktir. İki tablo arasında LEFT JOIN kullanıp sağ tabloda eşleşme olmayan kayıtları bulmak için IS NULL kullanabilirsiniz. Buna “anti-join” denir ve NOT IN ile NOT EXISTS alternatiflerine göre genellikle daha performanslıdır.
Senaryo: Hangi ürünlerin hiç sipariş edilmediğini bulmak istiyorsunuz.
-- Hiç sipariş edilmemiş ürünler (Anti-Join yöntemi)
SELECT
u.urun_id,
u.urun_adi,
u.kategori,
u.stok_miktari,
u.fiyat
FROM urunler u
LEFT JOIN siparis_detaylari sd ON u.urun_id = sd.urun_id
WHERE sd.urun_id IS NULL
ORDER BY u.urun_adi;
Bu yöntem büyük tablolarda NOT IN kullanmaktan çok daha hızlı çalışır. Özellikle milyonlarca kayıt içeren production tablolarında bu fark kritik önem taşır.
Benzer mantıkla, hangi müşterilerin son 6 ay içinde hiç alışveriş yapmadığını bulabilirsiniz:
-- Son 6 ayda hiç sipariş vermemiş müşteriler
SELECT
m.musteri_id,
m.musteri_adi,
m.email,
m.son_giris_tarihi
FROM musteriler m
LEFT JOIN siparisler s ON m.musteri_id = s.musteri_id
AND s.siparis_tarihi >= DATE_SUB(NOW(), INTERVAL 6 MONTH)
WHERE s.musteri_id IS NULL
AND m.aktif = 1;
Senaryo 4: Veri Temizleme ve Güncelleme İşlemleri
Production veritabanlarında zamanla kirli veri birikir. Özellikle eski sistemlerden migrate edilen veriler NULL değerlerle dolu olabilir. Bunları temizlemek için UPDATE sorgularında IS NULL kullanmanız gerekir.
-- NULL olan telefon numaralarını varsayılan değerle güncelle
UPDATE kullanicilar
SET telefon = 'Belirtilmemiş',
guncelleme_tarihi = NOW()
WHERE telefon IS NULL;
-- Belirli bir tarihten önce oluşturulmuş NULL değerli kayıtları güncelle
UPDATE urunler
SET son_guncelleme = olusturma_tarihi,
guncelleyen_kullanici = 'sistem_migrasyonu'
WHERE son_guncelleme IS NULL
AND olusturma_tarihi < '2023-01-01';
Büyük tablolarda bu tür toplu güncellemeleri yaparken dikkatli olun. Milyonlarca kaydı tek seferde güncellemek tabloyu kilitleyebilir. Batch işlemlerle yapmanızı öneririm:
-- Büyük tablolarda batch güncelleme
UPDATE kullanicilar
SET telefon = 'Belirtilmemiş'
WHERE telefon IS NULL
LIMIT 1000;
-- Bu sorguyu tüm NULL kayıtlar bitene kadar tekrarlayın
-- Bash script ile otomatize edebilirsiniz:
-- while mysql -u root -p'sifre' veritabani -e "UPDATE kullanicilar SET telefon='Belirtilmemiş' WHERE telefon IS NULL LIMIT 1000" | grep -q "1000 rows"; do sleep 1; done
NULL ile Aggregate Fonksiyonlar
COUNT, SUM, AVG gibi aggregate fonksiyonlar NULL değerleri farklı şekilde ele alır. Bu davranışı anlamak önemlidir.
-- COUNT(*) vs COUNT(kolon) farkı
SELECT
COUNT(*) AS toplam_kayit,
COUNT(telefon) AS telefon_dolu_kayit,
COUNT(*) - COUNT(telefon) AS telefon_null_kayit,
(COUNT(*) - COUNT(telefon)) * 100.0 / COUNT(*) AS null_yuzdesi
FROM kullanicilar;
COUNT(*) tüm satırları sayar, COUNT(kolon_adi) ise sadece NULL olmayan değerleri sayar. Bu fark, raporlama sorgularında kritik öneme sahiptir.
Önemli notlar:
- COUNT(*): Tüm satırları sayar, NULL dahil
- COUNT(kolon): Sadece NULL olmayan değerleri sayar
- SUM(kolon): NULL değerleri yok sayar, sanki 0’mış gibi değil, hiç yokmuş gibi davranır
- AVG(kolon): NULL değerleri hesaba katmaz, ortalamayı bozabilir
- MIN/MAX(kolon): NULL değerleri görmezden gelir
COALESCE ile NULL Değerleri Yönetmek
Sorgularda NULL yerine varsayılan değer göstermek istediğinizde COALESCE fonksiyonu devreye girer. Bu, IS NULL ile birlikte en çok kullanılan tekniktir.
-- COALESCE ile NULL yerine varsayılan değer göster
SELECT
musteri_adi,
COALESCE(telefon, 'Kayıtlı değil') AS telefon,
COALESCE(adres, 'Adres girilmemiş') AS adres,
COALESCE(indirim_orani, 0) AS indirim_orani,
siparis_tarihi,
toplam_tutar * (1 - COALESCE(indirim_orani, 0) / 100) AS net_tutar
FROM musteriler
LEFT JOIN siparisler USING(musteri_id);
COALESCE birden fazla argüman alır ve ilk NULL olmayan değeri döndürür. Bu özelliği birden fazla kaynak sütunu varken çok işe yarar:
-- Birden fazla iletişim kanalından ilk geçerli olanı al
SELECT
kullanici_id,
kullanici_adi,
COALESCE(is_telefonu, cep_telefonu, ev_telefonu, 'İletişim bilgisi yok') AS birincil_telefon,
COALESCE(is_email, kisisel_email, 'Email yok') AS birincil_email
FROM kullanicilar
WHERE aktif = 1;
IS NULL ile Index Kullanımı ve Performans
Sysadmin olarak performans her zaman önceliğimizdir. NULL değerler içeren sütunlarda index kullanımı hakkında bazı önemli noktalar var.
MariaDB ve MySQL’de NULL değerler index’e dahil edilir. Bu, IS NULL sorgularının da index kullanabileceği anlamına gelir. Ancak bazı eski kaynaklarda bunun mümkün olmadığı yazar, bu bilgi güncel değildir.
-- IS NULL sorgusunun index kullanıp kullanmadığını kontrol et
EXPLAIN SELECT * FROM siparisler WHERE kargo_tarihi IS NULL;
-- Partial index oluştur (sadece NULL olmayan değerler için)
-- Bu boyutu küçültür ve performansı artırabilir
CREATE INDEX idx_kargo_tarihi ON siparisler(kargo_tarihi);
-- NULL değerleri olan sütun için composite index
CREATE INDEX idx_durum_kargo ON siparisler(durum, kargo_tarihi);
EXPLAIN çıktısında type kolonuna bakın. ref veya range görüyorsanız index kullanılıyor demektir. ALL görüyorsanız full table scan yapılıyor, bu performans sorunu yaratabilir.
Performans ipuçları:
- Yüksek NULL yoğunluklu sütunlar: Eğer bir sütunun %90’ı NULL ise, bu sütun için ayrı bir index oluşturmak genellikle anlamsızdır
- Composite index: NULL kontrolünü diğer filtrelerle birleştiriyorsanız composite index daha etkilidir
- EXPLAIN kullanın: Her önemli NULL sorgusu öncesi EXPLAIN ile planı kontrol edin
- Partition’ları değerlendirin: Çok büyük tablolarda NULL değerleri ayrı bir partition’da tutmayı düşünebilirsiniz
Stored Procedure ile NULL Kontrolü
Tekrarlayan NULL kontrol sorgularını stored procedure’e almak bakımı kolaylaştırır.
-- Eksik profil bilgilerini raporlayan stored procedure
DELIMITER //
CREATE PROCEDURE EksikProfilRaporu(IN gun_sayisi INT)
BEGIN
SELECT
kullanici_id,
email,
kayit_tarihi,
CASE
WHEN telefon IS NULL AND dogum_tarihi IS NULL AND adres IS NULL
THEN 'Kritik - Çok eksik'
WHEN telefon IS NULL OR dogum_tarihi IS NULL
THEN 'Orta - Bazı bilgiler eksik'
ELSE 'Az - Minor eksiklik'
END AS eksiklik_seviyesi,
CONCAT_WS(', ',
IF(telefon IS NULL, 'Telefon', NULL),
IF(dogum_tarihi IS NULL, 'Doğum tarihi', NULL),
IF(adres IS NULL, 'Adres', NULL),
IF(sirket IS NULL, 'Şirket', NULL)
) AS eksik_alanlar
FROM kullanicilar
WHERE aktif = 1
AND (telefon IS NULL OR dogum_tarihi IS NULL OR adres IS NULL OR sirket IS NULL)
AND kayit_tarihi >= DATE_SUB(NOW(), INTERVAL gun_sayisi DAY)
ORDER BY kayit_tarihi DESC;
END //
DELIMITER ;
-- Kullanımı
CALL EksikProfilRaporu(30);
Sık Yapılan Hatalar
Hata 1: = NULL kullanmak
-- YANLIŞ - Bu sorgu hiçbir şey döndürmez
SELECT * FROM kullanicilar WHERE telefon = NULL;
-- DOĞRU
SELECT * FROM kullanicilar WHERE telefon IS NULL;
Hata 2: NOT IN ile NULL karışıklığı
-- TEHLIKELI - Subquery NULL dönürse tüm sorgu boş döner
SELECT * FROM musteriler
WHERE musteri_id NOT IN (SELECT musteri_id FROM kara_liste);
-- Eğer kara_liste tablosunda bir tane bile NULL musteri_id varsa
-- bu sorgu HİÇ sonuç döndürmez!
-- GÜVENLI - LEFT JOIN + IS NULL yöntemi kullan
SELECT m.* FROM musteriler m
LEFT JOIN kara_liste k ON m.musteri_id = k.musteri_id
WHERE k.musteri_id IS NULL;
Hata 3: NULL ile string karşılaştırması
-- YANLIŞ - '' (boş string) ve NULL farklı şeylerdir
SELECT * FROM urunler WHERE aciklama = '';
-- DOĞRU - Her iki durumu da kontrol et
SELECT * FROM urunler
WHERE aciklama IS NULL OR aciklama = '';
-- Ya da COALESCE kullan
SELECT * FROM urunler
WHERE COALESCE(aciklama, '') = '';
IFNULL ve NULLIF Fonksiyonları
MariaDB ve MySQL’de NULL yönetimi için iki kısayol fonksiyon daha var.
-- IFNULL: İkili alternatif (COALESCE'in iki argümanlı versiyonu)
SELECT
urun_adi,
IFNULL(stok_miktari, 0) AS stok,
IFNULL(son_satis_tarihi, 'Hiç satılmadı') AS son_satis
FROM urunler;
-- NULLIF: İki değer eşitse NULL döndür (sıfıra bölme hatalarını önlemek için)
SELECT
kategori,
toplam_gelir,
toplam_satis,
toplam_gelir / NULLIF(toplam_satis, 0) AS ortalama_satis_fiyati
FROM satis_ozeti;
-- Eğer toplam_satis 0 ise NULL döner, division by zero hatası almaz
NULLIF özellikle sıfıra bölme hatalarını önlemede çok kullanışlıdır. Production sorgularında bunu bir alışkanlık haline getirin.
Sonuç
NULL yönetimi, veritabanı sorgularının en önemli ama en çok göz ardı edilen konularından biridir. IS NULL ve IS NOT NULL operatörleri bu yönetimin temel taşlarıdır. Yanlış kullanıldığında sessiz hatalar yaratır; yani sorgunuz çalışır görünür ama beklediğiniz sonuçları vermez.
Bu yazıda ele aldığımız konuları özetlersek:
- NULL bir değer değil, değerin yokluğudur ve özel operatörle kontrol edilmelidir
IS NULLveIS NOT NULLher türlü SQL ifadesinde kullanılabilir- Anti-join tekniği,
NOT INalternatifine göre daha güvenli ve performanslıdır COALESCE,IFNULLveNULLIFNULL değerleri yönetmek için güçlü araçlardırNOT INile subquery kullanırken NULL değerlere dikkat edinEXPLAINile sorgu planınızı düzenli kontrol edin
Production veritabanlarında NULL kontrolü sadece teknik bir detay değil, veri bütünlüğünün ve uygulama güvenilirliğinin temel parçasıdır. Bu teknikleri günlük sysadmin rutininize dahil ettiğinizde, hem daha temiz verilerle hem de çok daha az “neden bu sorgu çalışmıyor?” sorusuyla karşılaşırsınız.
