EXISTS, IN ve NOT EXISTS: Fark, NULL Tuzağı ve Performans
EXISTS, IN, NOT IN ve NOT EXISTS arasındaki mantık farkı, NULL tuzağı, birleştirmeyle karşılaştırma ve performansı yürütme planıyla nasıl denetleyeceğiniz.
Bu yapılar aynı işi mi yapar?
EXISTS, IN ve NOT EXISTS, bir kayıt kümesindeki satırları başka bir kümede karşılığı olup olmamasına göre süzmek için kullanılır. Sorulan soru benzerdir: "bu müşterinin siparişi var mı", "bu ürün satılan ürünler arasında mı". Ama çalışma mantıkları farklıdır ve bazı durumlarda sonuçları da farklıdır.
IN, soldaki değeri bir değer listesi ya da alt sorgunun döndürdüğü sütunla karşılaştırır. EXISTS ise değer karşılaştırmaz; alt sorgunun en az bir satır döndürüp döndürmediğine bakar ve doğru ya da yanlış verir. Alt sorgunun hangi sütunları seçtiği önemli değildir, bu yüzden yaygın yazım SELECT içine sabit bir ifade koymaktır.
Bu yapıların ortak adı yarı birleştirmedir (semi join): eşleşme olup olmadığı önemlidir, eşleşen satırların sayısı önemli değildir. Karşı yapı olan anti birleştirme (anti join) ise karşılığı olmayan satırları arar.
Söz dizimi ve basit örnekler
Örneğin customers ve orders tablolarımız olsun. Siparişi olan müşteriler:
SELECT m.customer_id, m.ad FROM customers m WHERE EXISTS ( SELECT 'x' FROM orders s WHERE s.customer_id = m.customer_id );
Aynı sonucu IN ile de alabilirsiniz:
SELECT m.customer_id, m.ad FROM customers m WHERE m.customer_id IN (SELECT s.customer_id FROM orders s);
EXISTS örneğindeki alt sorgu dış sorgunun satırına bağlıdır; bu bağlı alt sorguya korelasyonlu alt sorgu denir. IN örneğindeki alt sorgu ise dış sorgudan bağımsızdır. Mantıksal olarak sonuç aynıdır, ancak veritabanının her satır için ne yaptığı bu yazımlarda aynı olmak zorunda değildir.
Birleştirme (JOIN) ile de benzer sonuç alınabilir, ama dikkat: bir müşterinin birden fazla siparişi varsa JOIN müşteriyi sipariş sayısı kadar tekrarlar. EXISTS ve IN müşteriyi bir kez döndürür. Yalnızca "var mı" sorusu soruluyorsa yarı birleştirme yapıları tekrar sorununu doğuştan çözer.
NOT IN ve NULL tuzağı
Burası bu konudaki en önemli noktadır. NOT IN, bir alt sorgunun sonucunda NULL değer varsa beklenmedik biçimde hiç satır döndürmez. Neden: SQL'de bir değer NULL ile karşılaştırıldığında sonuç doğru ya da yanlış değil, bilinmeyendir. NOT IN, listedeki her değerle eşitsizliğin doğru olmasını ister; listede tek bir NULL bulunması bütün koşulu bilinmeyen yapar ve satırlar elenir.
Örnek: sipariş vermemiş müşterileri bulmak için
SELECT m.customer_id FROM customers m WHERE m.customer_id NOT IN (SELECT s.customer_id FROM orders s);
yazarsanız ve orders tablosunda customer_id sütununda bir tane bile NULL varsa sonuç boş gelir. Sorgu hata vermez, sadece yanlış cevap verir. Bu yüzden insanlar bu sorunun farkına çoğunlukla veri büyüyünce ve biri raporu sorguladığında varır.
NOT EXISTS bu sorunu yaşamaz, çünkü değer karşılaştırmak yerine satır varlığına bakar:
SELECT m.customer_id FROM customers m WHERE NOT EXISTS ( SELECT 'x' FROM orders s WHERE s.customer_id = m.customer_id );
Anti birleştirme için LEFT JOIN ve IS NULL
Karşılığı olmayanları bulmanın bir yolu daha vardır: LEFT JOIN ile bağlayıp sağ taraftaki anahtarın NULL olduğu satırları süzmek.
SELECT m.customer_id FROM customers m LEFT JOIN orders s ON s.customer_id = m.customer_id WHERE s.customer_id IS NULL;
Bu yazım NULL tuzağından etkilenmez ve çoğu veritabanında NOT EXISTS ile aynı plana dönüşür. Dikkat edilecek nokta, IS NULL denetimini birleştirme anahtarında yapmaktır; yoksa gerçekten değeri NULL olan satırlarla karıştırırsınız. Okunabilirlik açısından tercihler değişir: NOT EXISTS niyeti en açık anlatan yazımdır, LEFT JOIN yazımı ise birleştirmeye alışkın okuyucular için tanıdıktır.
Performans: hangisi hızlıdır?
Kesin bir kural yoktur; cevap veritabanı sürümüne, indekslere ve veri dağılımına bağlıdır. Modern sorgu iyileştiricileri IN, EXISTS ve birçok durumda LEFT JOIN yazımını aynı iç planlara çevirebilir. Eski bir kural olan "alt sorgu küçükse IN, büyükse EXISTS" artık her sistemde geçerli değildir ve kuralı ezberlemek yerine ölçmek gerekir.
Ölçme yöntemi şöyledir:
- Sorgunun yürütme planını alın (örneğin EXPLAIN) ve yazımlar arasında plan farkı olup olmadığına bakın.
- Birleştirme sütunlarında indeks bulunduğundan emin olun; korelasyonlu alt sorgu indeks yoksa her dış satır için tam tarama yapabilir.
- Gerçek veri büyüklüğüyle deneyin; küçük test verisi planı yanıltır.
- NOT IN yerine NOT EXISTS yazmak çoğunlukla hem doğruluk hem plan açısından güvenlidir.
Bir sorgu yavaşsa önce neyi beklediğini plandan okuyun, sonra yazımı değiştirin.
Hangi durumda hangisi?
- Yalnızca "var mı" sorusu ve alt sorgu ayrı bir tablodan geliyorsa: EXISTS genellikle niyeti en net anlatır ve tekrar sorunu yoktur.
- Sabit küçük bir değer listesiyle karşılaştırma yapıyorsanız (durum IN (...)): IN okunabilirdir.
- "Karşılığı olmayanlar" soruluyorsa: NOT EXISTS ya da LEFT JOIN ile IS NULL; NOT IN yalnızca alt sorgu sütununun NULL içeremeyeceği kesinse (NOT NULL kısıtı varsa) güvenlidir.
- Eşleşen satırdan sütun da lazımsa: JOIN kullanın; yarı birleştirme yalnızca süzme yapar, sağ taraftan sütun getirmez.
- Birden fazla sütunla eşleştirme gerekiyorsa EXISTS daha rahattır; satır değeri karşılaştırmak veritabanına göre desteklenmeyebilir.
Ekip içinde tek bir kalıba bağlı kalmak bakımı kolaylaştırır; doğruluk söz konusu olduğunda ise kalıptan vazgeçin.
Bu beceriyi Mindset programlarında nerede çalışırsınız
Yarı ve anti birleştirme, alt sorgular ve korelasyon konusuyla, sorgu planını okuma becerisinin kesişiminde durur. Mindset'in SQL ile Veri Sorgulama (İleri) programında alt sorgular ve korelasyon teknikleri ile sorgu performansı ve yürütme planı analizi ayrı modüllerdir. SQL Performans Ayarı ve Sorgu Optimizasyonu programı ise sorgu planı analizi ve birleştirme ile toplama optimizasyonunu ele alır.
Rapor yazan analistler, veri ambarını besleyen mühendisler ve yavaş sorgu şikâyetleriyle uğraşan geliştirme ekipleri bu içerikten yararlanır. Kurumsal ekipler, NOT IN kullanımını kod incelemesi listesine eklemek gibi basit bir standartla yanlış sonuç riskini azaltabilir.
İlgili Mindset programı
SQL ile Veri Sorgulama (İleri)
Sık sorulan sorular
EXISTS içindeki SELECT neden sabit bir değerle yazılır?
EXISTS alt sorgunun döndürdüğü sütunlarla ilgilenmez, yalnızca en az bir satır dönüp dönmediğine bakar. Bu yüzden içeriye gerçek bir sütun yazmak ek bir anlam taşımaz. Sabit bir değer ya da yıldız yazımı fark yaratmaz; ekibinizin tercih ettiği kalıbı tutarlı kullanmanız yeterlidir.
NOT IN her zaman mı yanlış sonuç verir?
Hayır. Alt sorgunun sütunu NULL içermiyorsa NOT IN doğru çalışır. Sorun yalnızca listede NULL değer bulunduğunda çıkar. Sütunda NOT NULL kısıtı varsa risk yoktur; kısıt yoksa ya da emin değilseniz NOT EXISTS daha güvenli bir seçimdir.
IN listesine alt sorgu yerine çok sayıda sabit değer yazmak sorun olur mu?
Çok uzun sabit listeler sorgu metnini büyütür, planlamayı yavaşlatabilir ve bakımı zorlaştırır. Değerler bir kaynaktan geliyorsa onları geçici bir tabloya ya da ayrı bir tabloya alıp EXISTS veya JOIN ile bağlamak daha sürdürülebilirdir.
EXISTS ile JOIN arasında sonuç farkı olur mu?
Eşleşen satır birden fazlaysa olur. JOIN eşleşme sayısı kadar satır üretir, EXISTS ise her dış satırı bir kez döndürür. Yalnızca varlık denetiyorsanız EXISTS tekrarları baştan önler; JOIN kullanırsanız DISTINCT eklemeniz gerekebilir.
Performansı karşılaştırırken nelere dikkat etmeliyim?
Önbellek etkisini ortadan kaldırmak için sorguları birkaç kez çalıştırın, gerçek büyüklükte veriyle deneyin ve yalnızca süreye değil yürütme planına da bakın. Plan aynıysa yazımlar arasında hız farkı beklemeyin; fark çıkıyorsa nedeni genellikle eksik indekstir.