Menu

SQLite Subquery: WHERE, FROM ve SELECT'te İç İçe Sorgular

SQLite'ta bir SELECT'i başka bir SELECT'in içine nasıl yerleştirirsiniz? Scalar subquery, IN/EXISTS, türetilmiş tablolar, korelasyonlu subquery'ler ve JOIN ne zaman daha okunaklı olur, hepsi burada.

Bu sayfada çalıştırılabilir editörler var — düzenle, çalıştır ve sonucu anında gör.

SQLite Alt Sorgu: SELECT İçinde SELECT

SQLite alt sorgu (subquery), adından da anlaşılacağı gibi başka bir sorgunun içine, parantezler arasına yerleştirilmiş bir SELECT ifadesidir. SQLite önce içteki sorguyu çalıştırır, çıkan sonucu alır ve dıştaki sorguya iletir.

Tekrar tekrar kullanacağımız küçük bir örnek hazırlayalım:

Beş sipariş, dört müşteri ve bunlardan ikisi hiç sipariş vermemiş. Bu tabloyu yazı boyunca kullanacağız.

WHERE içinde subquery: Listeye göre filtreleme

En sık karşılaşacağın kalıp şu: iç sorguda bir id listesi çekersiniz, sonra dış sorguyu bu listeye göre filtrelersiniz.

İç sorgu, orders tablosunda geçen tüm customer_id değerlerini döndürür. Dış sorgu ise yalnızca id değeri bu listede yer alan müşterileri tutar. Sonuçta Cleo, Boris ve Ada görünür; siparişi olmayan Dmitri ise listeye girmez.

IN (SELECT ...), "B tablosunda eşleşmesi olan A satırlarını getir" mantığının en sık kullanılan kalıbıdır. Zihninizde şöyle okuyun: "bu kolonun değeri, iç sorgunun döndürdüğü değerlerden biri olan satırları getir."

NOT IN: NULL'lara dikkat

Tam tersi soru — "hangi müşteriler sipariş vermemiş?" — yalnızca tek satır uzakta:

Burada işe yarıyor. Ama NOT IN'in sinsi bir tarafı var: alt sorgu bir tek NULL döndürürse, tüm NOT IN ifadesi NULL'a dönüşür (ki bu TRUE değildir) ve sonuçta hiç satır gelmez. Hem şaşırtıcı hem de sessiz sedasız olan bir hata.

NULL içerebilecek bir kolona karşı NOT IN kullanırken alışkanlık haline getirmeniz gereken güvenli yaklaşım:

Ya da bu sorunu hiç yaşamayan NOT EXISTS kullanabilirsiniz. Ona da geleceğiz.

Skaler Subquery: Tek Satır, Tek Sütun

Skaler alt sorgu tek bir değer döner — bir satır, bir sütun — ve bir değerin beklendiği her yerde kullanabilirsiniz.

İçteki SELECT MAX(total) FROM orders sorgusu 200 değerini döndürür. Dış sorgu da bu değere eşit olan siparişleri filtreler. Bir agregasyon sonucuyla karşılaştırma yapmanız gereken her durumda işine yarar.

Aynı şekilde, SELECT listesinde scalar subquery kullanarak her satıra hesaplanmış bir değer ekleyebilirsiniz:

customers tablosundaki her satır, kendi customers.id değerini iç sorguya yerleştirip onu bir kez çalıştırır. İşte buna correlated (bağıntılı) subquery deniyor — birazdan daha ayrıntılı ele alacağız. Bu örnekteki gibi "her satır için tek bir sayı" senaryolarında genellikle GROUP BY ile birlikte kullanılan bir LEFT JOIN daha performanslı olur, ama scalar subquery formu okunuşu açısından çok şık duruyor.

EXISTS kullanımı: Sadece eşleşme var mı diye bak

EXISTS, IN operatörünün daha sessiz akrabası gibidir. Değerlerle ilgilenmez; sadece subquery'nin herhangi bir satır döndürüp döndürmediğine bakar. İçeride genellikle SELECT 1 yazarsınız, çünkü hangi sütun olduğunun bir önemi yok.

Bu sorgu, en az bir tane 100 üzeri sipariş vermiş müşterileri bulur. İçteki sorgu, dıştaki sorgudan c.id'yi referans alıyor — işte bu yüzden ona correlated subquery deniyor. SQLite, içteki tabloda eşleşmeyi bulduğu anda taramayı kesiyor; "bu satırın ilişkili bir kaydı var mı?" tarzı sorularda EXISTS'in IN'e kıyasla genelde daha hızlı çalışmasının sebebi de bu.

Bunun olumsuzu olan NOT EXISTS ise "ilişkili kayıt yok" demenin NULL-güvenli yoludur:

FROM İçinde Subquery: Türetilmiş Tablo

SQLite'ta bir subquery, tablo kullanılabilen her yerde kullanılabilir; FROM ifadesi de buna dahil. İçteki sorgu, üzerinde join yapabileceğin, filtreleyebileceğin veya toplama (aggregate) işlemleri uygulayabileceğin geçici ve isimli bir "türetilmiş tablo"ya dönüşür.

İç sorgu, her müşteri için toplamı hesaplar. Dış sorgu ise bu toplamların ülke bazında ortalamasını alır. Bu tarz iki aşamalı toplulaştırmalar için türetilmiş tablolar (derived table) tam aranan çözümdür — yani tek bir GROUP BY ile her şeyi halledemediğinizde.

AS per_customer takma adı zorunludur: her türetilmiş tablonun bir ismi olmak zorundadır.

Korelasyonlu Subquery: Her Dış Satır İçin Çalışan Alt Sorgu

Bir alt sorgu, dış sorgudaki bir sütuna referans veriyorsa korelasyonlu (correlated) sayılır. Bu durumda SQLite, iç sorguyu her dış satır için yeniden çalıştırmak zorunda kalır. Esnek bir yaklaşımdır ama maliyeti hızlıca artabilir.

Her müşteri için en büyük siparişi bulalım. İçteki sorgu customers.id değerine bağlı olduğundan her müşteri için ayrı ayrı çalışır. Sipariş vermemiş müşteriler için sonuç NULL olur — ki zaten istediğimiz davranış budur.

Korelasyonlu alt sorgular (correlated subquery), "A tablosundaki her satır için B'den bir şey hesapla" mantığına çok yakışır. Tablo küçükse ya da arama indeksli bir kolon üzerindeyse gönül rahatlığıyla kullanabilirsiniz. Ama büyük tablolarda ve uygun indeks yoksa, canlıya çıkmadan önce mutlaka performans testi yapın — bu gibi durumlarda GROUP BY ile birlikte kullanılan bir JOIN çoğu zaman daha hızlı sonuç verir.

SQLite Subquery vs JOIN: Hangisini Seçmeli?

Aşağıdaki iki sorgu aynı soruya cevap veriyor:

İkisi de aynı satırları döndürür. SQLite'ın optimize edicisi zaten birini diğerine dönüştürüyor çoğu zaman. Yani seçimi okunabilirliğe göre yapın:

  • Sadece filtreleme yapacaksanız ve iç tablodan sütun istemiyorsanız subquery (alt sorgu) kullanın.
  • Sonuçta iki tablodan da sütun gerekiyorsa JOIN tercih edin.
  • "Bununla ilişkili en az bir satır var mı?" sorusunu soruyorsanız EXISTS kullanın — hem daha anlaşılır olur hem de IN/NOT IN'in NULL tuzaklarına düşmezsiniz.

Kararsız kaldığınızda, sesli okuduğunuzda kendini açıklayan versiyonu yazın.

Sık Yapılan Hata: Birden Fazla Satır Döndüren Subquery

= ile kullanılan bir subquery en fazla bir satır döndürmek zorundadır. Daha fazla satır dönerse SQLite birini seçer (pratikte rastgele) ve hata vermeden yanlış sonuç alırsınız — sessiz sedasız.

IN, iç sorgunun birden fazla satır döndürebileceği durumlarda kullanılır:

Tek bir satır bekliyorsanız ve bunu garanti altına almak istiyorsanız, LIMIT 1 ekleyin ve yanına bir ORDER BY koyun ki en azından hangi satırın geleceği belli olsun. Daha da iyisi: sorguyu, verinin doğası gereği tek satır döndürecek şekilde yazın (örneğin benzersiz bir sütuna göre filtreleyin).

Sırada: Common Table Expression'lar

FROM içindeki alt sorgular işin içinden çıkılmaz bir hâl alabiliyor — hele aynı türetilmiş tabloya iki kez ihtiyacınız varsa ya da iç içe geçmiş üç seviyelik bir yapıya bakıyorsanız. Common Table Expression'lar (WITH ... AS (...)) sayesinde bir alt sorguya en başta isim verip sorgunun geri kalanında o isimle referans verebilirsiniz. Sonraki sayfanın konusu bu.

Sıkça Sorulan Sorular

SQLite'ta subquery nedir?

Subquery, parantez içine alınıp başka bir sorgunun içine yerleştirilen bir SELECT ifadesidir. SQLite önce içteki sorguyu çalıştırır, sonucu da dıştaki sorguya besler. Subquery'leri WHERE, FROM, SELECT ve daha birkaç clause içinde kullanabilirsiniz.

SQLite'ta IN ile EXISTS arasındaki fark nedir?

IN (SELECT ...) bir değerin, subquery'nin döndürdüğü satırlardan herhangi birine eşit olup olmadığını kontrol eder. EXISTS (SELECT ...) ise sadece subquery'den en az bir satır gelip gelmediğine bakar; değerlerle ilgilenmez. Eğer içteki sorgu dıştaki satırı referans alıyorsa (yani korelasyonlu bir subquery'yse) genelde EXISTS daha iyi bir tercihtir.

Subquery mi kullanmalıyım, JOIN mi?

Sonuçta iki tablodan da kolon görmek istiyorsanız JOIN kullanın. Sadece filtreleme yapacaksanız ya da tek bir değer hesaplayacaksanız subquery yeterli. Zaten SQLite optimizer'ı çoğu zaman birini diğerine çeviriyor, o yüzden hangisi daha okunaklıysa onu seçin.

Korelasyonlu (correlated) subquery nedir?

Korelasyonlu subquery, dıştaki sorgudan bir kolonu referans alan subquery'dir; bu yüzden dıştaki her satır için tekrar tekrar çalıştırılması gerekir. Esnektir ama büyük tablolarda yavaşlayabilir. Performans profilinizde böyle bir subquery öne çıkıyorsa, onu bir JOIN'e ya da CTE'ye dönüştürmek çoğu zaman işe yarar.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA