Sorunun içinde başka bir soru
“Ortalamanın üstünde ödeme yapan rezervasyonlar” derken önce ortalamayı bilmek gerekir. Alt sorgu bu ara sonucu üretir:
SELECT COUNT(*) AS adet FROM hv_bookings WHERE price > (SELECT AVG(price) FROM hv_bookings);
Var mı, yok mu? EXISTS
SELECT COUNT(*) AS hic_rezervasyonu_yok FROM hv_passengers p WHERE NOT EXISTS (SELECT 1 FROM hv_bookings b WHERE b.passenger_id = p.passenger_id);
Grupları genel ortalamayla karşılaştırmak
SELECT f.route_id, ROUND(AVG(ROUND((julianday(f.actual_dep) - julianday(f.sched_dep)) * 1440)), 1) AS ort_gecikme FROM hv_flights f WHERE f.actual_dep IS NOT NULL GROUP BY f.route_id HAVING AVG(ROUND((julianday(f.actual_dep) - julianday(f.sched_dep)) * 1440)) > (SELECT AVG(ROUND((julianday(f.actual_dep) - julianday(f.sched_dep)) * 1440)) FROM hv_flights f WHERE f.actual_dep IS NOT NULL) ORDER BY ort_gecikme DESC LIMIT 5;
Derinleş: NOT IN ve NULL tuzağı, skaler alt sorgunun yeri
NOT IN ile NOT EXISTS çoğu zaman aynı sonucu verir. Arada tek ama tehlikeli bir fark vardır: alt sorgu bir NULL döndürürse NOT IN hiçbir satır döndürmez.
Sebep üç değerli mantıktır. x NOT IN (a, b, NULL) ifadesi x <> a AND x <> b AND x <> NULL demektir. x <> NULL her zaman UNKNOWN olduğu için bütün ifade hiçbir zaman TRUE olamaz. Aşağıda 26 Aralık 2024'te hiç inişi olmayan uçakları iki yoldan arıyoruz. Alt sorgudaki CASE, inmemiş (iptal) uçuşlar için NULL üretir, tıpkı boş bir sütun gibi:
SELECT 'NOT EXISTS' AS yontem, COUNT(*) AS ucak
FROM hv_aircraft a
WHERE NOT EXISTS (SELECT 1 FROM hv_flights f
WHERE f.tail_no = a.tail_no AND f.status = 'landed'
AND f.sched_dep >= '2024-12-26' AND f.sched_dep < '2024-12-27')
UNION ALL
SELECT 'NOT IN', COUNT(*)
FROM hv_aircraft a
WHERE a.tail_no NOT IN (SELECT CASE WHEN f.status = 'landed' THEN f.tail_no END
FROM hv_flights f
WHERE f.sched_dep >= '2024-12-26' AND f.sched_dep < '2024-12-27');
NOT EXISTS 5 uçak bulur. NOT IN ise 0 döndürür, çünkü o gün 3 iptal var ve alt sorgu üç NULL üretir. Sorgu hata vermez, yalnızca sessizce yanlış sonuç verir. Gerçek tablolarda boş bırakılabilen her sütun (email, seat, manager_id …) bu riski taşır.
| Durum | Tercih |
|---|---|
| “Karşılığı olmayanlar” | NOT EXISTS (NULL'a dayanıklı) |
| NOT IN kullanmak zorundaysan | Alt sorguya WHERE sütun IS NOT NULL ekle |
| “Karşılığı olanlar” | IN ya da EXISTS; NULL burada sorun çıkarmaz |
| Satır başına genel bir değerle kıyas | Skaler alt sorgu (SELECT AVG(...)) ya da CTE |
Skaler alt sorgu nereye yazılır?
Tek değer döndüren bir alt sorgu SELECT, WHERE ve HAVING içinde kullanılabilir. “Rotanın ortalama ücreti, genel ortalamanın kaç katı?” sorusunda alt sorgu hem SELECT'te (oran) hem HAVING'de (filtre) görünür. Okunabilirlik ya da performans sorun olursa genel ortalamayı bir CTE'de bir kez hesaplayıp CROSS JOIN ile bağlamak aynı işi yapar. Çoğu veritabanı ilişkisiz skaler alt sorguyu zaten bir kez hesaplar.
b2.passenger_id = b.passenger_id) ilişkilidir ve kavramsal olarak her dış satır için yeniden çalışır. Büyük tablolarda bunu bir JOIN ya da pencere fonksiyonuyla yeniden yazmak çoğu zaman daha hızlıdır.