SQL'de Alt Sorgu ve CTE: Yazım, Seçim Ölçütü ve Hatalar
SQL alt sorgu türlerini, WITH ile CTE yazımını, özyinelemeli sorguları ve NOT IN gibi sık hataları örneklerle adım adım öğrenin.
Alt sorgu ve CTE ne işe yarar?
Alt sorgu (subquery), başka bir sorgunun içine yerleştirilen SELECT ifadesidir. Dıştaki sorgu, içteki sorgunun sonucunu bir değer, bir değer listesi ya da geçici bir tablo gibi kullanır. CTE (common table expression, ortak tablo ifadesi) ise WITH anahtar sözcüğüyle sorgunun başında adlandırılan geçici bir sonuç kümesidir. İkisi de karmaşık bir soruyu adımlara bölmek için vardır; fark, adımları nereye ve nasıl yazdığınızdadır.
Ortalamanın üzerinde maaş alan çalışanları bulmak klasik örnektir: Önce ortalama hesaplanır, sonra her çalışan bu değerle karşılaştırılır. Tek bir ifadede yazılamayan bu çok aşamalı mantığı alt sorgu ya da CTE ile kurarsınız. Okunabilirlik ve bakım kolaylığı çoğu zaman gerekçenin kendisidir.
Alt sorgu türleri: skaler, satır listesi, tablo ve ilişkili
Alt sorgular döndürdükleri şekle göre ayrılır: • Skaler alt sorgu: Tek bir değer döndürür. WHERE salary > (SELECT AVG(salary) FROM employees) gibi karşılaştırmalarda ya da SELECT listesinde hesaplanan sütun olarak kullanılır. Birden fazla satır dönerse hata verir. • Liste döndüren alt sorgu: IN ve NOT IN ile kullanılır. Örneğin sipariş vermiş müşterilerin listesini IN ile filtrelersiniz. • Tablo (türetilmiş) alt sorgu: FROM maddesine yazılır ve takma ad alır. Ara sonuçları toplayıp ardından üzerinde ek bir işlem yapmak için uygundur. • İlişkili (correlated) alt sorgu: İçteki sorgu dıştaki sorgunun sütununa başvurur ve dış sorgunun her satırı için yeniden değerlendirilir. Örneğin her müşterinin son siparişini bulmak için iç sorguda dış sorgunun müşteri numarasına bağlanırsınız.
EXISTS ve NOT EXISTS ayrı bir kalıptır: Alt sorgu satır döndürüyor mu sorusunu yanıtlar, değerlerle ilgilenmez. "Hiç siparişi olmayan müşteriler" için NOT EXISTS genellikle hem açık hem güvenlidir.
CTE nasıl yazılır?
CTE, ana sorgudan önce WITH ile tanımlanır, bir ad alır ve sonra normal bir tablo gibi çağrılır. Örnek kalıp: WITH monthly AS (SELECT customer, SUM(amount) AS total FROM orders GROUP BY customer) SELECT * FROM monthly WHERE total > avg_threshold. Mantık sorgunun başında okunur, ana sorgu kısa kalır.
Birden fazla CTE virgülle ayrılarak zincirlenir ve sonrakiler öncekilere başvurabilir. Böylece "ham veriyi temizle, ardından toplulaştır, ardından sırala" gibi akışlar yukarıdan aşağıya doğru okunabilir adımlara dönüşür. Aynı ara sonucu sorguda birkaç kez kullanacaksanız tanımı bir kez yazmak da hatayı azaltır.
Bir CTE yalnızca tanımlandığı sorgu boyunca yaşar; kalıcı bir nesne değildir. Kalıcı olmasını istiyorsanız görünüm (view) ya da tablo kullanılır. Veritabanı motoruna göre CTE'nin sonucu bir kez hesaplanıp yeniden kullanılabilir ya da her çağrıldığında satır içi açılabilir. Bu davranış sürümler arasında değişebildiğinden performans kritik sorgularda yürütme planına bakmak gerekir.
Ne zaman alt sorgu, ne zaman CTE?
Kısa, tek yerde kullanılan ve tek bir değer üreten koşullar için alt sorgu yeterli ve doğaldır. Mantık birkaç aşamaya yayıldığında, aynı ara sonuç tekrar kullanıldığında ya da sorguyu başkasının okuyup değiştirmesi bekleniyorsa CTE tercih edilir. İç içe birkaç katmanlı alt sorgu, hata ayıklamayı zorlaştırır; aynı mantık adlandırılmış CTE'lerle yazıldığında her adımı ayrı ayrı çalıştırıp doğrulayabilirsiniz.
Örnek bir iş akışı düşünün: Önce iade edilen siparişleri ayıklayan bir CTE, ardından müşteri başına net satışı hesaplayan ek bir CTE, son olarak da bunları müşteri tablosuyla birleştiren ana sorgu. Her aşama kendi adıyla anılır ve hangi satırın nerede elendiğini görmek kolaylaşır.
Performans açısından genel bir kural yoktur. Birçok motor, alt sorguyu ve CTE'yi aynı yürütme planına dönüştürebilir; fark çoğunlukla okunabilirliktedir. Yine de ilişkili alt sorgu büyük tablolarda her dış satır için tekrar çalışabileceğinden, JOIN ya da pencere fonksiyonuyla yeniden yazmak denenmelidir.
Özyinelemeli (recursive) CTE: hiyerarşik veri
Özyinelemeli CTE, kendi sonucuna başvurarak hiyerarşileri gezmenize olanak verir. Yapısı başlangıç ve tekrar kısmından oluşur: Başlangıç (anchor) sorgusu kökleri seçer, özyinelemeli kısım ise bir önceki adımın sonuçlarını alıp bir kademe daha ilerler. UNION ALL ile birleşir ve yeni satır gelmeyince durur.
Tipik kullanımlar: organizasyon şemasında bir yöneticinin altındaki tüm çalışanları listelemek, ürün kategori ağacını yoldan kökten yaprağa açmak, malzeme listesi (bill of materials) patlatmak ve bir tarih aralığındaki tüm günleri üretmek. Çalışan tablosunda her satırın yönetici numarası varsa, anchor kısmı en üstteki kişiyi, özyinelemeli kısım da yöneticisi önceki adımdaki kişi olan satırları getirir. Bir derinlik sütunu eklemek, raporda girintili gösterim için işe yarar.
Dikkat edilecek nokta döngüdür. Veride bir kayıt dolaylı olarak kendi üstüne bağlıysa sorgu sonsuza dek çalışabilir. Motorların çoğu bir özyineleme sınırı koyar; yine de ziyaret edilen yolu tutmak ya da derinliği sınırlamak iyi bir savunmadır. Sözdizimi motora göre değişir: Bazı sistemlerde WITH RECURSIVE yazılır, bazılarında yalnızca WITH yeterlidir.
Sık yapılan hatalar
- NOT IN ile NULL tuzağı: Alt sorgunun sonuç listesinde bir tane bile NULL varsa NOT IN beklenmedik biçimde hiç satır döndürmeyebilir. NOT EXISTS ya da LEFT JOIN ile IS NULL kontrolü daha güvenlidir.
- Skaler alt sorgunun birden çok satır döndürmesi: Veri değiştiğinde dün çalışan sorgu bugün hata verir. Eşsizlik bekleniyorsa bunu sorguda koşulla sağlayın.
- Türetilmiş tabloya takma ad vermemek: Çoğu motor FROM içindeki alt sorgu için ad ister.
- CTE'yi kalıcı sanmak: Her sorguda yeniden tanımlanır. Sık kullanılan mantığı görünüme taşıyın.
- Gereksiz CTE zinciri: Her adım için ayrı CTE açmak da okunabilirliği bozar. Adım gerçekten ayrı bir anlam taşıyorsa ayırın.
- Özyinelemede çıkış koşulu bırakmamak: Sonsuz döngü riskini göz ardı etmeyin.
Sorguyu yazarken adım adım ilerleyin: İçteki sorguyu tek başına çalıştırın, sonucunu kontrol edin, sonra dış sorguya bağlayın. Bu alışkanlık hata ayıklama süresini belirgin biçimde kısaltır.
Öğrenme yolu
Önce JOIN ve GROUP BY'da rahat olun; alt sorgu ve CTE bu temelin üstüne kurulur. Başlangıç düzeyinde skaler alt sorgu ile IN kullanımını pratik edin, ardından aynı soruları EXISTS ve JOIN ile yeniden çözün. Bu yöntemlerin sonuçlarını kıyaslamak, hangisinin hangi durumda doğal olduğunu hissettirir.
İleri düzeyde ilişkili alt sorguları pencere fonksiyonlarıyla değiştirmeyi, CTE zincirlerini kurmayı ve özyinelemeli CTE ile hiyerarşik veriyi gezmeyi çalışın. Yürütme planı okumayı da bu aşamada öğrenin; sorgunun nasıl çalıştığını görmek, hangi yazım biçiminin sizin veritabanınızda hızlı olduğunu anlamanın tek güvenilir yoludur.
Bu beceriyi Mindset programlarında nerede çalışırsınız?
SQL ile Veri Sorgulama (İleri) eğitiminin müfredatında Ortak Tablo İfadeleri (CTE) ve Recursive Sorgular ile Alt Sorgular ve Korelasyon Teknikleri modülleri yer alır.
Aynı programdaki Sorgu Performansı ve Yürütme Planı Analizi ile Pencere Fonksiyonları ile İleri Analiz modülleri, bu sayfada anlatılan yazım seçeneklerini karşılaştırma ve değiştirme becerisini tamamlar.
İlgili Mindset programı
SQL ile Veri Sorgulama (İleri)
Sık sorulan sorular
CTE alt sorgudan daha mı hızlıdır?
Genel olarak hayır. CTE esas olarak okunabilirlik ve yeniden kullanım sağlar; birçok motor, alt sorguyu ve CTE'yi benzer bir yürütme planına çevirir. Bazı sistemlerde CTE bir kez hesaplanıp saklanabilir, bazılarında satır içi açılır. Kritik bir sorguda gerçek farkı görmek için kendi veritabanınızda yürütme planını karşılaştırın.
İlişkili alt sorgu ne zaman sorun çıkarır?
Dış sorgunun her satırı için iç sorgu yeniden değerlendirildiğinden, büyük tablolarda yavaşlama yaşanabilir. İç sorguda ilgili sütunda dizin yoksa durum daha da kötüleşir. Çözüm olarak sorguyu JOIN, toplulaştırılmış türetilmiş tablo ya da pencere fonksiyonuyla yeniden yazmak, çoğu zaman yeterli iyileşme sağlar.
IN ile EXISTS arasında hangisini seçmeliyim?
Sonuç olarak çoğu durumda aynı satırları döndürürler. EXISTS, yalnızca eşleşme olup olmadığına baktığı ve NULL konusunda daha öngörülebilir davrandığı için özellikle NOT biçiminde tercih edilir. IN, kısa ve sabit değer listelerinde okunaklıdır. Modern optimizasyon motorları ikisini de benzer biçimde işleyebilir.
Özyinelemeli CTE hangi durumlarda kullanılır?
Satırların birbirine bağlandığı hiyerarşik ve ağ benzeri verilerde kullanılır: organizasyon şeması, kategori ağacı, malzeme listesi ya da tarih serisi üretimi. Her adımda bir önceki adımın sonucuna bakarak ilerler. Sonsuz döngüye karşı derinlik sınırı koymak ve veride döngüsel referans olup olmadığını kontrol etmek gerekir.
CTE içinde veri değiştirmek mümkün mü?
Bu motora bağlıdır. PostgreSQL gibi bazı sistemler INSERT, UPDATE ve DELETE ifadelerini CTE içinde kullanmaya izin verir; birçok sistemde CTE yalnızca okuma amaçlıdır ve sonrasındaki DML ifadesine kaynak olur. Kullandığınız veritabanının belgesini kontrol edin.
İlgili rehberler
Kategori: Veri Analitiği ve İş Zekası