MySQL'de NULLIF Fonksiyonunu Kullanmak

Selamlar, bu yazımda MySQL'in NULLIF fonksiyonuna bakacağız. Konu küçük gibi duruyor ama gerçekten doğru yerde kullandığınızda epeyce kod tasarrufu sağlıyor; özellikle CASE WHEN zincirlerini gereksiz yere uzattığım eski sorgularıma dönüp baktığımda, 'keşke o zaman bilseydim' dediklerim oldu. Lafı çok uzatmadan başlayalım.

NULLIF nedir?

NULLIF(ifade1, ifade2) çok basit bir kuralla çalışır: iki argüman birbirine eşitse NULL döner, değilse birinci argümanı olduğu gibi geri verir. Yani aslında elimizde şunun kısa yazılışı var:

CASE WHEN ifade1 = ifade2 THEN NULL ELSE ifade1 END

IFNULL ile mantıken tam zıt iş yapıyor: IFNULL NULL gördüğü zaman onu bir varsayılana çevirirken, NULLIF belirli bir değeri görünce onu NULL'a çeviriyor. Bu zıtlık size garip geldiyse şöyle düşünün: NULLIF 'şu değer aslında veri değil, görmezden gel' demenin sorgu içindeki yoludur.

Birkaç hızlı örnekle netleşsin:

SELECT NULLIF(10, 10);     -- NULL
SELECT NULLIF(10, 20);     -- 10
SELECT NULLIF('a', 'a');   -- NULL
SELECT NULLIF(NULL, 5);    -- NULL (zaten NULL geldiği için)

Sıfıra bölme tuzağı

NULLIF'i hayatımda en çok kullandığım yer burası. Bir oran, ortalama ya da yüzde hesaplarken paydanın sıfır olma ihtimali varsa sorgu ya hata atar ya warning üretir. Sentinel olarak NULL döndürtmek hem temiz hem de aşağı yukarı standart pratik:

SELECT
  store_id,
  total_sales / NULLIF(total_orders, 0) AS avg_order_value
FROM store_stats;

Burada total_orders sıfır olduğunda payda NULL olur, sonuç da NULL çıkar; sıfıra bölme uyarısı görmezsiniz. Eğer dashboard'da boşluk yerine sıfır görmek isterseniz IFNULL ile sarmak yeterli:

SELECT
  store_id,
  IFNULL(total_sales / NULLIF(total_orders, 0), 0) AS avg_order_value
FROM store_stats;

Bence bu kalıbı bir kere ezberleyin, raporlama sorgularınızın yarısında işinize yarar.

Sentinel değerleri NULL'a çevirmek

Eski veri setlerinde 'eksik veri' yerine 'N/A', 'unknown', -1 gibi sentinel'lar tutulur. Bu değerleri olduğu gibi AVG, COUNT(DISTINCT), MIN, MAX gibi agregatlara sokunca sonuç bozulur. NULLIF ile temizlenmiş halini geçirmek hayatı kolaylaştırır:

SELECT
  AVG(NULLIF(age, -1))                      AS avg_age,
  COUNT(NULLIF(phone, 'N/A'))               AS real_phone_count,
  COUNT(DISTINCT NULLIF(category, ''))      AS category_count
FROM contacts;

AVG ve COUNT(expr) zaten NULL değerleri görmezden gelir; iş sentinel'ı NULL'a çevirmekte. Bunu uygulama tarafında WHERE phone <> 'N/A' ile yapmaya kalkarsanız, aynı sorguda birden fazla sütun için ayrı filtre yazmak gerekir; NULLIF her sütun için tek satır.

Veri temizliği yaparken de aynı mantık çalışıyor:

UPDATE users
SET middle_name = NULLIF(TRIM(middle_name), '');

Boş string'leri gerçek NULL'a indirgemek için bence en kestirme yol bu. TRIM ile beraber kullanmak özellikle Excel'den aktarılmış kolonlarda hayat kurtarıyor.

NULLIF, IFNULL ve COALESCE - hangisi ne zaman?

Karıştırılan üçlü bu. Kısaca farkı:

  • NULLIF(x, y): x = y ise NULL döner. Belirli bir değeri 'yok say' demek.
  • IFNULL(x, y): x NULL ise y, değilse x döner. İki argüman alır.
  • COALESCE(x1, x2, ..., xn): İlk NULL olmayan değeri döner. Çok argümanlı IFNULL gibi düşünebilirsiniz.

Üçünü beraber zincirleyince güzel ifadeler çıkıyor. Mesela ekibimde sıkça kullandığımız bir kalıp - kişinin 'gerçek' bir telefonunu bulmak, sentinel'ları atlayarak:

SELECT
  name,
  COALESCE(
    NULLIF(mobile_phone, 'N/A'),
    NULLIF(home_phone, 'N/A'),
    'Telefon yok'
  ) AS best_phone
FROM contacts;

NULLIF ile sentinel'ı NULL'a çeviriyoruz, COALESCE da ilk anlamlı değeri seçiyor.

Sık karşılaşılan hatalar

  • Tip uyuşmazlığı: NULLIF(int_kolon, '0') yazarsanız MySQL implicit cast yapar ama her zaman beklediğiniz gibi davranmaz. İki argümanın tipi aynı olsun; sayısalsa sayısal, string ise string.
  • NULLIF vs IS NULL karıştırmak: NULLIF bir karşılaştırma operatörü değil, dönüşüm fonksiyonu. WHERE NULLIF(x, 0) yazmak istediğiniz şey değil; orada büyük ihtimalle WHERE x <> 0 istiyorsunuz.
  • Index kullanımını engellemek: Sorgunuzun WHERE bloğunda NULLIF(col, sentinel) kullanmak indekslerin devre dışı kalmasına yol açar. SELECT listesinde özgürce kullanın, ama WHERE'de col <> sentinel daha planner-dostu olur.
  • Boş string ile NULL aynı şey sanmak: NULLIF(col, '') yazdığınızda sadece tam boş string yakalanır. Tek boşluk içeren satırlar için NULLIF(TRIM(col), '') kombinasyonu gerek.

Kapanış

NULLIF küçük bir fonksiyon ama doğru yerde kullanınca hem sıfıra bölmeyi temiz halleder hem de kirli verideki sentinel'ları agregatlara sokmadan önce eler. Bana sorarsanız bu fonksiyonu öğrendikten sonra CASE WHEN ... = ... THEN NULL zincirlerini bir daha yazmak istemezsiniz. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.