---
title: 'Alt Sorgular'
source: 'https://academia.sh/tr/kurslar/ileri-sql/alt-sorgular'
course: 'İleri SQL'
language: tr
updated: '2026-08-17T18:08:54+00:00'
license: 'CC BY-SA 4.0'
---

# Alt Sorgular

Skaler, satır ve tablo alt sorgularının yazıldığı yerler, türetilmiş tablolar, IN ile EXISTS ayrımı ve boş değerlerin alt sorgu sonucunu sessizce boşaltması.

SQL Temelleri kursu, tek bir sonuç kümesi üreten deyimleri kurdu: satır seçmek, tabloları
birleştirmek, gruplayıp özetlemek. Bu deyimlerin ortak yanı, hesabın **tek adımda**
bitmesidir. Oysa gerçek sorular çoğu zaman iki adımlıdır: "ortalamanın üstünde ödünç alan
üyeler" sorusu, önce ortalamayı, sonra ondan büyük olanları ister.

Bu tür soruları iki ayrı sorguyla çözüp sonucu elde birleştirmek mümkündür, ancak veri
sorgular arasında değişebilir ve ara sonucun taşınması gereksiz iştir. SQL'in yanıtı, bir
sorgunun içine başka bir sorgu yerleştirmektir. Önceki kursta bu yerleştirme yalnız koşul
yazarken kullanıldı; burada kendi başına bir yapı olarak ele alınır. Bu ders, yerleştirmenin
biçimlerini ve her birinin nereye yazılabildiğini kurar.

## Alt Sorgu ve Döndürdüğü Şekil

**Alt sorgu (subquery)**, başka bir deyimin içine parantezle gömülmüş bir `SELECT`
deyimidir. Nereye yazılabileceğini belirleyen tek şey, **döndürdüğü şekildir**:

| Şekil | Ne döndürür | Nerede kullanılır |
|---|---|---|
| Skaler alt sorgu | Tek satır, tek sütun | Bir değerin geçebildiği her yer |
| Satır alt sorgusu | Tek satır, çok sütun | Satır karşılaştırmalarında |
| Tablo alt sorgusu | Çok satır, çok sütun | `FROM`, `IN`, `EXISTS` |

Bu üçlü, dersin iskeletidir. Şekil uymadığında motor hata verir; şekil uyduğu hâlde
beklenmedik sonuç çıkıyorsa neredeyse her zaman sorumlu boş değerlerdir — dersin son
bölümü bunu ele alır.

Kurs boyunca aynı örnek üzerinde çalışılacak: bir kütüphanenin ödünç kayıtları. Dört tablo
vardır — `sube` (şubeler), `uye` (üyeler), `kitap` (kitaplar) ve `odunc` (ödünç işlemleri).
Her kod bloğu ihtiyaç duyduğu tabloları kendi içinde kurar; blok olduğu gibi kopyalanıp
çalıştırıldığında yazılan çıktıyı üretir.

## Skaler Alt Sorgu

Skaler alt sorgu tek bir değer üretir; bu yüzden bir sabitin ya da sütun adının
yazılabildiği her yere yazılabilir. En sık kullanıldığı yer, karşılaştırmanın sağ
tarafıdır.

"Üye başına düşen ortalama ödünç sayısından fazla kitap alan üyeler" sorusu, `HAVING`
içinde bir skaler alt sorguyla ifade edilir:

```bash
sqlite3 -box -header <<'SQL'
CREATE TABLE uye(id INTEGER PRIMARY KEY, ad TEXT, sube_id INT);
CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT, uye_id INT, alis TEXT, iade TEXT);
INSERT INTO uye VALUES (1,'Ayse',4),(2,'Burak',4),(3,'Ceren',5),(4,'Deniz',5),
                       (5,'Emre',6),(6,'Fatma',6),(7,'Gokhan',4),(8,'Hale',5);
INSERT INTO odunc VALUES
  (1,1,1,'2024-03-01','2024-03-15'),(2,3,1,'2024-03-01','2024-03-20'),
  (3,5,1,'2024-03-04','2024-03-18'),(4,7,1,'2024-03-11',NULL),
  (5,9,1,'2024-03-18','2024-03-29'),(6,2,2,'2024-03-01','2024-03-12'),
  (7,4,2,'2024-03-06','2024-03-25'),(8,6,2,'2024-03-11','2024-03-19'),
  (9,8,2,'2024-03-21',NULL),(10,1,3,'2024-03-04','2024-03-10'),
  (11,3,3,'2024-03-06','2024-03-27'),(12,10,3,'2024-03-13','2024-03-22'),
  (13,2,3,'2024-03-25',NULL),(14,5,4,'2024-03-04','2024-03-09'),
  (15,7,4,'2024-03-13','2024-03-26'),(16,4,4,'2024-03-20',NULL),
  (17,6,5,'2024-03-06','2024-03-14'),(18,9,5,'2024-03-11','2024-03-23'),
  (19,1,5,'2024-03-25','2024-03-28'),(20,8,6,'2024-03-04','2024-03-17'),
  (21,10,6,'2024-03-13','2024-03-21'),(22,3,6,'2024-03-20',NULL),
  (23,2,7,'2024-03-11','2024-03-16'),(24,5,7,'2024-03-18','2024-03-24'),
  (25,4,8,'2024-03-13','2024-03-24');

SELECT u.ad, COUNT(*) AS adet
FROM odunc o JOIN uye u ON u.id = o.uye_id
GROUP BY u.id, u.ad
HAVING COUNT(*) > (SELECT COUNT(*) * 1.0 / COUNT(DISTINCT uye_id) FROM odunc)
ORDER BY adet DESC, u.ad;
SQL
```

```text
┌───────┬──────┐
│  ad   │ adet │
├───────┼──────┤
│ Ayse  │ 5    │
│ Burak │ 4    │
│ Ceren │ 4    │
└───────┴──────┘
```

Alt sorgu, dış sorgunun hiçbir sütununa başvurmaz. Bu bağımsızlık iki sonuç doğurur:
motor onu **bir kez** hesaplar ve sonucu sabit gibi kullanır; ayrıca alt sorgu tek başına
çalıştırılıp doğrulanabilir. Alt sorgu ile dış sorgu arasında bağ kurulduğunda tablo
değişir — sonraki dersin konusu budur.

Skaler alt sorgunun sözleşmesi katıdır: **en fazla bir satır** döndürmelidir. Sıfır satır
döndürürse sonuç boş değerdir; birden çok satır döndürürse standart bir hata gerektirir.
Bu yüzden skaler alt sorgular ya bir toplama işlevi içerir ya da anahtara göre süzülür.

## Satır Alt Sorgusu

Karşılaştırma yalnız tek değerler arasında yapılmak zorunda değildir. Standart SQL, satır
değerlerini de karşılaştırır: soldaki parantezli sütun listesiyle sağdaki alt sorgunun
döndürdüğü satır, sütun sütun eşlenir.

```bash
sqlite3 -box -header <<'SQL'
CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT, uye_id INT, alis TEXT, iade TEXT);
INSERT INTO odunc VALUES
  (2,3,1,'2024-03-01','2024-03-20'),(11,3,3,'2024-03-06','2024-03-27'),
  (22,3,6,'2024-03-20',NULL),(3,5,1,'2024-03-04','2024-03-18');
SELECT id, kitap_id, uye_id, alis
FROM odunc
WHERE (kitap_id, alis) = (SELECT kitap_id, MIN(alis) FROM odunc WHERE kitap_id = 3);
SQL
```

```text
┌────┬──────────┬────────┬────────────┐
│ id │ kitap_id │ uye_id │    alis    │
├────┼──────────┼────────┼────────────┤
│ 2  │ 3        │ 1      │ 2024-03-01 │
└────┴──────────┴────────┴────────────┘
```

Satır karşılaştırması, "şu kitabın **ilk** ödünç kaydı" gibi iki koşulu birlikte tutan
soruları tek ifadeyle yazar. Aynı sonuç iki ayrı skaler alt sorguyla da elde edilebilir,
ancak o yazımda iki alt sorgunun aynı satırdan geldiği görsel olarak kaybolur.

Satır değeri karşılaştırması standarttadır, fakat desteklenme derecesi motora göre
değişir; sıralama işleçleriyle (`<`, `>`) birleştirildiğinde daha da değişir. Taşınabilir
kod yazılıyorsa hedef motorun desteğini doğrulamak gerekir.

## Tablo Alt Sorgusu ve Türetilmiş Tablolar

Çok satır döndüren bir alt sorgu, bir tablo gibi davranır. İki kullanımı vardır.

Birincisi, `FROM` içinde **türetilmiş tablo (derived table)** olarak yer almasıdır. Bu,
gruplamanın sonucunu yeniden gruplamak gibi iki katmanlı hesapların standart yoludur:

```bash
sqlite3 -box -header <<'SQL'
CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT, uye_id INT, alis TEXT, iade TEXT);
INSERT INTO odunc VALUES
  (1,1,1,'2024-03-01','2024-03-15'),(2,3,1,'2024-03-01','2024-03-20'),
  (3,5,1,'2024-03-04','2024-03-18'),(4,7,1,'2024-03-11',NULL),
  (5,9,1,'2024-03-18','2024-03-29'),(6,2,2,'2024-03-01','2024-03-12'),
  (7,4,2,'2024-03-06','2024-03-25'),(8,6,2,'2024-03-11','2024-03-19'),
  (9,8,2,'2024-03-21',NULL),(10,1,3,'2024-03-04','2024-03-10'),
  (11,3,3,'2024-03-06','2024-03-27'),(12,10,3,'2024-03-13','2024-03-22'),
  (13,2,3,'2024-03-25',NULL),(14,5,4,'2024-03-04','2024-03-09'),
  (15,7,4,'2024-03-13','2024-03-26'),(16,4,4,'2024-03-20',NULL),
  (17,6,5,'2024-03-06','2024-03-14'),(18,9,5,'2024-03-11','2024-03-23'),
  (19,1,5,'2024-03-25','2024-03-28'),(20,8,6,'2024-03-04','2024-03-17'),
  (21,10,6,'2024-03-13','2024-03-21'),(22,3,6,'2024-03-20',NULL),
  (23,2,7,'2024-03-11','2024-03-16'),(24,5,7,'2024-03-18','2024-03-24'),
  (25,4,8,'2024-03-13','2024-03-24');

SELECT MIN(adet) AS en_az, MAX(adet) AS en_cok, ROUND(AVG(adet), 2) AS ortalama
FROM (SELECT uye_id, COUNT(*) AS adet FROM odunc GROUP BY uye_id) AS sayim;
SQL
```

```text
┌───────┬────────┬──────────┐
│ en_az │ en_cok │ ortalama │
├───────┼────────┼──────────┤
│ 1     │ 5      │ 3.13     │
└───────┴────────┴──────────┘
```

Toplama işlevi doğrudan iç içe yazılamaz; `AVG(COUNT(*))` geçersizdir. Türetilmiş tablo,
iç hesabı bir tabloya dönüştürerek dış hesabın önünü açar. Türetilmiş tabloya **takma ad
verilmesi** zorunludur (burada `sayim`), çünkü dış sorgu ona bir adla başvurur.

İkinci kullanım, tablo alt sorgusunun bir **liste** olarak süzmede yer almasıdır:
`WHERE id IN (SELECT …)`. Bu yazım okunaklıdır; ancak listeye boş değer karıştığında
sessizce yanlış davranır.

## Boş Değerin Listeyi Boşaltması

Kütüphanede kayıp bildirimleri ayrı bir tabloda tutulsun. Bir bildirim, hangi kitaba ait
olduğu henüz belirlenememişse `kitap_id` sütunu boş bırakılarak kaydedilir. "Kayıp
bildirimi olan kitaplar" ve "olmayan kitaplar" sorgularının ikisi de `IN` ile yazılabilir:

```bash
sqlite3 -box -header <<'SQL'
CREATE TABLE kitap(id INTEGER PRIMARY KEY, baslik TEXT, tur TEXT);
INSERT INTO kitap VALUES (1,'Kayip Zaman','roman'),(2,'Sessiz Ev','roman'),
                         (3,'Sayilar Kurami','bilim'),(4,'Evrenin Yapisi','bilim');
CREATE TABLE kayip(id INTEGER PRIMARY KEY, kitap_id INT, bildirim TEXT);
INSERT INTO kayip VALUES (1,2,'2024-03-08'),(2,NULL,'2024-03-19');

SELECT baslik FROM kitap WHERE id IN (SELECT kitap_id FROM kayip);
SELECT baslik FROM kitap WHERE id NOT IN (SELECT kitap_id FROM kayip);
SQL
```

```text
┌───────────┐
│  baslik   │
├───────────┤
│ Sessiz Ev │
└───────────┘
```

İlk sorgu beklendiği gibi bir satır verdi. İkinci sorgu **hiçbir satır** vermedi; bu
yüzden çıktıda yalnız bir tablo görünüyor. Oysa üç kitabın kayıp bildirimi yok.

Nedeni, İlişkisel Kuram kursunda tanıtılan üç değerli mantıktır. `x NOT IN (a, b)` ifadesi
`x <> a AND x <> b` demektir. Listede boş değer varsa o karşılaştırma doğru ya da yanlış
değil, **bilinmeyen** olur; bir çarpımın bilinmeyen çarpanı sonucu bilinmeyene çeker ve
bilinmeyen satır süzgeçten geçmez:

```bash
sqlite3 -box -header <<'SQL'
CREATE TABLE kitap(id INTEGER PRIMARY KEY, baslik TEXT, tur TEXT);
INSERT INTO kitap VALUES (1,'Kayip Zaman','roman'),(2,'Sessiz Ev','roman'),
                         (3,'Sayilar Kurami','bilim'),(4,'Evrenin Yapisi','bilim');
CREATE TABLE kayip(id INTEGER PRIMARY KEY, kitap_id INT, bildirim TEXT);
INSERT INTO kayip VALUES (1,2,'2024-03-08'),(2,NULL,'2024-03-19');

SELECT 3 IN (2, NULL) AS "3 IN (2,NULL)", 3 NOT IN (2, NULL) AS "3 NOT IN (2,NULL)";
SELECT baslik FROM kitap k
WHERE NOT EXISTS (SELECT 1 FROM kayip y WHERE y.kitap_id = k.id);
SQL
```

```text
┌───────────────┬───────────────────┐
│ 3 IN (2,NULL) │ 3 NOT IN (2,NULL) │
├───────────────┼───────────────────┤
│               │                   │
└───────────────┴───────────────────┘
┌────────────────┐
│     baslik     │
├────────────────┤
│ Kayip Zaman    │
│ Sayilar Kurami │
│ Evrenin Yapisi │
└────────────────┘
```

İlk satırdaki iki boş hücre, iki ifadenin de boş değer ürettiğini gösterir: ne doğru ne
yanlış. `EXISTS` ise farklı bir soruyu sorar — "eşleşen satır **var mı**" — ve yanıtı her
zaman doğru ya da yanlıştır; boş değer üretmez. Bu yüzden `NOT EXISTS` üç kitabı da
döndürdü.

Kural olarak: alt sorgunun döndürdüğü sütunda boş değer olabiliyorsa `NOT IN` yerine
`NOT EXISTS` yazılır ya da alt sorguya `WHERE kitap_id IS NOT NULL` koşulu eklenir.
Sütunun boş değer alamayacağı şemada güvenceye alınmışsa `NOT IN` güvenlidir.

## Alt Sorgu Nereye Yazılabilir

Alt sorgunun yazılabildiği dört konum ve her birinin tipik işi şudur:

- `SELECT` listesinde: satır başına bir ek değer üretmek.
- `FROM` içinde: ara sonucu tablo hâline getirmek.
- `WHERE` içinde: satırları başka bir sorgunun sonucuna göre süzmek.
- `HAVING` içinde: grupları başka bir sorgunun sonucuna göre süzmek.

`SELECT` listesindeki alt sorgu okunaklı olsa da maliyeti gizler:

```bash
sqlite3 -box -header <<'SQL'
CREATE TABLE uye(id INTEGER PRIMARY KEY, ad TEXT, sube_id INT);
CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT, uye_id INT, alis TEXT, iade TEXT);
INSERT INTO uye VALUES (1,'Ayse',4),(2,'Burak',4),(3,'Ceren',5),(4,'Deniz',5);
INSERT INTO odunc VALUES
  (1,1,1,'2024-03-01','2024-03-15'),(2,3,1,'2024-03-01','2024-03-20'),
  (4,7,1,'2024-03-11',NULL),(6,2,2,'2024-03-01','2024-03-12'),
  (9,8,2,'2024-03-21',NULL),(10,1,3,'2024-03-04','2024-03-10');

SELECT u.ad,
       (SELECT COUNT(*) FROM odunc o WHERE o.uye_id = u.id) AS toplam,
       (SELECT MAX(alis) FROM odunc) AS son_islem_tarihi
FROM uye u
ORDER BY u.id;
SQL
```

```text
┌───────┬────────┬──────────────────┐
│  ad   │ toplam │ son_islem_tarihi │
├───────┼────────┼──────────────────┤
│ Ayse  │ 3      │ 2024-03-21       │
│ Burak │ 2      │ 2024-03-21       │
│ Ceren │ 1      │ 2024-03-21       │
│ Deniz │ 0      │ 2024-03-21       │
└───────┴────────┴──────────────────┘
```

Çıktıdaki iki sütun aynı görünse de yapıları farklıdır. Üçüncü sütundaki alt sorgu dış
sorguya bağlı değildir; tek kez hesaplanıp her satıra kopyalanır. İkinci sütundaki alt
sorgu ise `u.id` üzerinden dış satıra bağlıdır: her üye için ayrıca değerlendirilmesi
gerekir. Bu bağ, alt sorgunun maliyetini satır sayısıyla çarpar.

Ayrıca bu yazımın bir yan yararı görünür: hiç ödünç kaydı olmayan `Deniz` için sıfır
çıktı. İç birleştirmeyle yazılsaydı bu üye sonuçtan tümüyle düşerdi ve onu geri getirmek
için dış birleştirme gerekirdi; alt sorgu eşleşme yokken sıfır üretti.

## Özet

- Alt sorgu, döndürdüğü şekle göre skaler, satır ve tablo alt sorgusu olarak ayrılır;
  şekil, nereye yazılabileceğini belirler.
- Skaler alt sorgu en fazla bir satır döndürmelidir; sıfır satır boş değer üretir.
- `FROM` içindeki türetilmiş tablo, toplama işlevlerini katmanlamanın standart yoludur ve
  takma ad almak zorundadır.
- Alt sorgunun sütununda boş değer olabiliyorsa `NOT IN` sessizce boş sonuç verir;
  `NOT EXISTS` üç değerli mantıktan etkilenmediği için doğru yanıtı verir.
- Dış sorgunun sütununa başvurmayan alt sorgu bir kez hesaplanır; başvuran alt sorgu satır
  başına değerlendirilir.

## Sonraki Adım

Bu dersin son örneğinde iki alt sorgu yan yana duruyordu: biri tek kez, diğeri satır
başına hesaplanan. Aradaki fark yazımda tek bir sütun başvurusundan ibaretti, ancak iş
miktarındaki fark katsayıyla ölçülür. Sonraki ders, dış satıra bağlı olan bu ikinci
biçimi — ilişkili alt sorguyu — tanımlayacak, kaç kez değerlendirildiğini sayarak
ölçecek ve aynı sonucu üreten birleştirmeli yazımla karşılaştıracak.
