SQL JOIN sonucunda satır sayısı artıyorsa, karşı tabloda aynı anahtara sahip birden fazla kayıt var demektir. JOIN satırları kendiliğinden çoğaltmaz; eşleşme koşulu bir satırın karşı tarafta birden fazla kayıtla eşleşmesine izin veriyorsa sonuç kümesi büyür. Bu yazıda küçük bir örnekle artışın nereden geldiğini görecek, ardından sebebi bulmak için üç adımlı bir teşhis yöntemi uygulayacaksın.
JOIN neden bir öğrenciyi birden fazla satıra çevirir?
JOIN, koşulu sağlayan her eşleşmeyi sonuca ayrı bir satır olarak yazar. İki tabloyu ogrenci_id üzerinden bağladığında soldaki bir satırın sağda kaç eşi varsa o kadar satır üretilir.
Burada iki rol var. ogrenciler.ogrenci_id birincil anahtardır (primary key): her öğrenciyi tek başına tanımlar ve tekrar etmez. kayitlar.ogrenci_id ise yabancı anahtardır (foreign key): kaydın hangi öğrenciye ait olduğunu gösterir ve aynı değer birçok satırda bulunabilir. Bu düzene birden çoğa ilişki denir: bir öğrencinin birçok kaydı olabilir, bir kayıt tek öğrenciye aittir.
Başlangıç durumu: her öğrencinin en fazla bir kaydı var
ogrenciler tablosunda dört öğrenci var: Ayşe (1), Can (2), Elif (3) ve Deniz (4). kayitlar tablosunda ise üç kayıt var: Ayşe-Matematik, Can-Fizik, Elif-Kimya. Deniz'in kaydı yok.
SELECT o.ad, k.ders
FROM ogrenciler o
INNER JOIN kayitlar k ON o.ogrenci_id = k.ogrenci_id;
Beklenen çıktı 3 satırdır; Deniz eşleşmediği için INNER JOIN sonucunda yer almaz:
| ad | ders |
|---|---|
| Ayşe | Matematik |
| Can | Fizik |
| Elif | Kimya |
Can ikinci bir derse kaydolunca ne olur?
Can'a ikinci bir ders kaydı ekle:
INSERT INTO kayitlar (ogrenci_id, ders) VALUES (2, 'Biyoloji');
Aynı JOIN sorgusunu yeniden çalıştırdığında sonuç 4 satıra çıkar. Satırların sırası ORDER BY kullanmadığın sürece değişebilir, önemli olan Can'ın iki kez görünmesidir:
| ad | ders |
|---|---|
| Ayşe | Matematik |
| Can | Fizik |
| Can | Biyoloji |
| Elif | Kimya |
Sorgu hata vermedi ve JOIN doğru çalıştı. Can gerçekten iki derse kayıtlı olduğu için iki satır görmen gerekir; artış ilişkinin doğasından gelir. Sorun, bu artışı beklemeden sorguyu yazdığında ortaya çıkar. Örneğin öğrenci başına tek satır isteyen bir rapor bu sonuçla yanlış olur.
Satır sayısı arttığında nasıl teşhis edersin? Üç adımlı çerçeve

Sonuç beklediğinden büyükse tahmin yürütmek yerine sırayla şu üç adımı uygula:
- İlişki türünü belirle. İki tablo arasındaki bağ bire bir mi, birden çoğa mı, çoktan çoğa mı? Sorguyu çalıştırmadan önce beklenen satır sayısını tahmin et. Birden çoğa ilişkide sonuç, çoklu taraftaki eşleşen kayıt sayısı kadar satır içerir. Bizim örnekte 4 kayıt olduğundan 4 satır beklemen gerekir.
- Anahtarın benzersizliğini COUNT ile kontrol et. Hangi anahtarın tekrar ettiğini şu sorgu gösterir:
Beklenen çıktı yalnızca Can'ı (SELECT ogrenci_id, COUNT(*) AS kayit_sayisi FROM kayitlar GROUP BY ogrenci_id HAVING COUNT(*) > 1;ogrenci_id2) gösterir:
Aynı sonuca iki sayıyı karşılaştırarak da ulaşabilirsin:ogrenci_id kayit_sayisi 2 2 SELECT COUNT(*) AS toplam, COUNT(DISTINCT ogrenci_id) AS benzersiz FROM kayitlar;
İki sayı eşit değilse sütunda tekrar eden değer vardır.toplam benzersiz 4 3 ogrencilertablosunda aynı kontrolü yaptığında ikisi de 4 çıkar, çünkü birincil anahtar tekrar etmez. - JOIN koşulunu gözden geçir.
ONifadesi var mı, doğru sütunları mı eşliyor, benzersiz olması gereken taraftaki sütun gerçekten benzersiz mi?
İlk iki adım bu yazıdaki Can örneğinde olduğu gibi ilişkiden doğan artışı, üçüncü adım ise sonraki bölümlerde ele alınan INNER JOIN ile LEFT JOIN davranışını, eksik koşulu ve yanlış sütunla eşleştirmeyi yakalar.
INNER JOIN ve LEFT JOIN satır sayısını nasıl etkiler?
Örneklerde ogrenciler tablosunda Ayşe (1), Can (2), Deniz (3) ve Elif (4) var. kayitlar tablosunda dört kayıt duruyor: 1 numaralı kayıt Ayşe'nin Matematik dersi, 2 numaralı kayıt Can'ın Fizik dersi, 3 numaralı kayıt Elif'in Kimya dersi, 4 numaralı kayıt Can'ın Python dersi. Deniz'in hiç kaydı yok.
INNER JOIN yalnızca eşleşenleri döndürür
SELECT o.ad, k.ders
FROM ogrenciler o
INNER JOIN kayitlar k ON o.ogrenci_id = k.ogrenci_id
ORDER BY o.ogrenci_id, k.kayit_id;
Beklenen çıktı 4 satırdır. Deniz'in karşı tarafta eşleşeni olmadığı için sonuçta görünmez:
| ad | ders |
|---|---|
| Ayşe | Matematik |
| Can | Fizik |
| Can | Python |
| Elif | Kimya |
LEFT JOIN soldaki tablonun her satırını korur
SELECT o.ad, k.ders
FROM ogrenciler o
LEFT JOIN kayitlar k ON o.ogrenci_id = k.ogrenci_id
ORDER BY o.ogrenci_id, k.kayit_id;
Beklenen çıktı 5 satırdır. Deniz artık listede, ders sütunu NULL olarak gelir:
| ad | ders |
|---|---|
| Ayşe | Matematik |
| Can | Fizik |
| Can | Python |
| Deniz | NULL |
| Elif | Kimya |
Satır sayısı tek başına doğruluk göstergesi değildir
LEFT JOIN sonucu, soldaki tablonun satır sayısının altına düşmez. Ama bir öğrenci karşı tarafta birden fazla kayıtla eşleşirse sayı bunun üstüne çıkabilir. Yukarıdaki 4 öğrenciye karşılık 5 satır tam olarak bunu gösteriyor.
INNER JOIN ise iki yönde birden etki eder: eşleşmeyenleri eler, çok eşleşenleri çoğaltır. Biri satır sayısını düşürüp diğeri yükselttiği için toplam tesadüfen beklediğin sayıya denk gelebilir. Bu yüzden "4 satır bekliyordum, 4 satır geldi" demek sonucun doğru olduğunu kanıtlamaz.
Eksik JOIN koşulu ve yanlış sütunla eşleştirme neden ayrı birer hatadır?

İkisi de satır sayısını bozabilir ama nedenleri farklıdır. Birinde eşleşme koşulu hiç yoktur, diğerinde koşul vardır ama yanlış şeyi karşılaştırır.
Eksik koşul: kartezyen çarpım
Eski virgüllü yazımda tablolar FROM satırında virgülle sıralanır, eşleşme koşulu ise WHERE içine yazılır. Koşul unutulursa veri tabanı elindeki tüm kombinasyonları üretir: her öğrenci her kayıtla eşleşir. Buna kartezyen çarpım denir.
SELECT COUNT(*)
FROM ogrenciler o, kayitlar k;
| COUNT(*) |
|---|
| 16 |
4 öğrenci ile 4 kaydın çarpımı 16 satır eder. Tabloların büyüklüğü arttıkça bu sayı hızla büyür; 1000 satırlık iki tablo bir milyon satır üretir. Koşulu WHERE o.ogrenci_id = k.ogrenci_id olarak eklediğinde sonuç yine 4 satıra iner. JOIN ... ON yazımında koşul sözdiziminin parçası olduğu için bu hatayı yapmak daha zordur.
Yanlış sütun: hata vermeyen sessiz bozulma
Koşul yazılmış ama anahtar olmayan bir sütunla kurulmuş olabilir:
SELECT o.ad, k.ders
FROM ogrenciler o
INNER JOIN kayitlar k ON o.ogrenci_id = k.kayit_id
ORDER BY o.ogrenci_id;
Sorgu sorunsuz çalışır ve 4 satır döndürür; sayı makul görünür. Ancak eşleşmelere bakınca sorun ortaya çıkar:
| ad | ders |
|---|---|
| Ayşe | Matematik |
| Can | Fizik |
| Deniz | Kimya |
| Elif | Python |
İlk iki satır tesadüfen doğru. Ama kaydı hiç olmayan Deniz, Elif'in Kimya kaydıyla eşleşti; Elif'e de Can'ın Python kaydı yazıldı. Hata mesajı yok, satır sayısı da şüphe uyandırmıyor.
Karşılaştırdığın sütun iki tarafta da tekrar eden değerler içeriyorsa durum daha da kötüleşir: eşleşmeler çoğalır ve satır sayısı da şişer. Çıkarım şu: satır sayısı doğru görünse bile eşleşmelerin anlamını ayrıca kontrol etmelisin. Bir JOIN koşulunda yabancı anahtarın karşısına birincil anahtar gelmelidir; bu örnekte k.ogrenci_id, o.ogrenci_id ile eşleşmelidir.
DISTINCT neden sorunu çözmez, yalnızca gizler?
Satır sayısı arttığında akla ilk gelen çözüm SELECT DISTINCT eklemektir. Tekrar eden satırlar kaybolur ve sonuç düzelmiş gibi görünür:
SELECT DISTINCT o.ad
FROM ogrenciler o
INNER JOIN kayitlar k ON o.ogrenci_id = k.ogrenci_id;
| ad |
|---|
| Ayşe |
| Mert |
| Zeynep |
Üç ad döndü, ama JOIN yine dört satır üretti. DISTINCT yalnızca bu satırları en sonda tekilleştirdi. Sorgu sayma ya da toplamaya girdiğinde fark ortaya çıkar:
SELECT COUNT(*)
FROM ogrenciler o
INNER JOIN kayitlar k ON o.ogrenci_id = k.ogrenci_id;
| COUNT(*) |
|---|
| 4 |
Öğrenci sayısını soruyordun ama kayıt sayısını (4) aldın. Çoğalan satırlar sayıma da toplama da girer. Bu yüzden DISTINCT'i kök neden çözümü saymak yanlış olur.
Kök nedene inen iki çözüm
Kayıt sayısı gibi bir özet istiyorsan önce kayitlar tablosunu öğrenci başına tek satıra indir, sonra birleştir:
SELECT o.ad, k.kayit_sayisi
FROM ogrenciler o
INNER JOIN (
SELECT ogrenci_id, COUNT(*) AS kayit_sayisi
FROM kayitlar
GROUP BY ogrenci_id
) k ON o.ogrenci_id = k.ogrenci_id;
| ad | kayit_sayisi |
|---|---|
| Ayşe | 2 |
| Mert | 1 |
| Zeynep | 1 |
İhtiyaç yalnızca "kaydı olan öğrenciler" ise kayıt satırlarını sonuca taşımaya gerek yoktur. EXISTS her öğrenciyi en fazla bir kez döndürür:
SELECT o.ad
FROM ogrenciler o
WHERE EXISTS (
SELECT 1 FROM kayitlar k
WHERE k.ogrenci_id = o.ogrenci_id
);
| ad |
|---|
| Ayşe |
| Mert |
| Zeynep |
DISTINCT'in meşru bir yeri de var: sonuçta gerçekten tekrarsız değerler listelemek istiyorsan kullanabilirsin. Önemli olan bunu bilinçli bir tercih olarak yapmandır. Öğrencileri saymak istiyorsan COUNT(DISTINCT o.ogrenci_id) da çalışır, ama ad yerine anahtarı saymalısın. İki farklı öğrencinin adı aynı olabilir ve DISTINCT onları tek kişi gibi gösterir.
JOIN sonucunu doğrulamak için kısa kontrol listesi
Sorguyu çalıştırmadan önce ve çalıştırdıktan sonra bu sırayla ilerleyebilirsin:
- Sorguyu çalıştırmadan önce beklediğin satır sayısını tahmin et.
- İki tablo arasındaki ilişki türünü yaz: bire bir mi, bire çok mu?
- Karşı tablodaki anahtarı
COUNT(*)veCOUNT(DISTINCT ...)ile karşılaştırarak benzersizliğini kontrol et. ONkoşulunun var olduğunu ve doğru sütunları eşlediğini doğrula.- Eşleşmeyen satırların sonuçta kalıp kalmayacağına göre
INNER JOINya daLEFT JOINseç. DISTINCTeklemeden önce satırların neden çoğaldığını bul.- Toplam ya da sayı alırken önce özetle, sonra birleştir.
Beklenen sayıyı önceden tahmin etme ve sonucu bu tahminle karşılaştırma alışkanlığı SQL'in dışında da işe yarar. Algoritma yazarken ya da hata ayıklarken de aynı yolu izleyebilirsin. Seviyeni genel olarak ölçmek istersen ücretsiz bilgi testleri merkezine göz atabilirsin.
Bir JOIN'in sonucu beklediğinden büyükse önce satırların nereden geldiğine bak. Sorunun kaynağı çoğu zaman ilişki türü, anahtar ya da ON koşuludur.