INNER, LEFT, RIGHT, FULL OUTER, CROSS ve SELF dahil olmak üzere her analistin bilmesi gereken 6 SQL JOIN'i pratik örnekler ve yaygın tuzaklarla öğrenin.

Bir SQL JOIN, iki (veya daha fazla) tablonun ilişkili bir sütun üzerinde—genellikle bir ID—eşleşen satırlarını tek bir sonuçta birleştirmenizi sağlar.
Gerçek dünya veritabanları çoğunlukla aynı bilgiyi tekrar etmemek için ayrı tablolara bölünür. Örneğin bir müşterinin adı customers tablosunda, satın alımları ise orders tablosunda saklanır. JOIN'ler, bu parçaları gerektiğinde yeniden bir araya getirmenin yoludur.
Bu nedenle JOIN'ler raporlama ve analizde her yerde karşımıza çıkar:
JOIN'ler olmasa ayrı sorgular çalıştırıp sonuçları elle birleştirmeniz gerekecekti—yavaş, hata yapmaya açık ve tekrarlanması zor.
Eğer ilişkisel bir veritabanı üzerinde ürünler (paneller, yönetici ekranları, dahili araçlar, müşteri portalları) geliştiriyorsanız, JOIN'ler ham tabloları kullanıcıya gösterilen görünümlere dönüştürür. Koder.ai gibi platformlar (React + Go + PostgreSQL uygulamaları sohbetten üretenler) bile doğru liste sayfaları, raporlar ve mutabakat ekranları gerektiğinde sağlam JOIN mantığına dayanır—çünkü veritabanı mantığı hızlansa da ortadan kaybolmaz.
Bu rehber, günlük SQL işlerinin çoğunu kapsayan altı JOIN türüne odaklanır:
JOIN sözdizimi çoğu SQL veritabanında (PostgreSQL, MySQL, SQL Server, SQLite) çok benzerdir. Bazı farklılıklar vardır—özellikle FULL OUTER JOIN desteği ve bazı uç durum davranışlarında—ama kavramlar ve temel kalıplar kolayca aktarılır.
JOIN örneklerini basit tutmak için üç küçük tablo kullanacağız: müşteriler sipariş verir ve siparişlerin ödemesi olabilir (veya olmayabilir).
Küçük bir not: örnek tablolar aşağıda sadece birkaç sütunu gösteriyor ama sonraki sorgularda order_date, created_at, status veya paid_at gibi tipik alanlara referans verilecektir. Bu sütunları üretim şemalarında sık bulunan tipik alanlar olarak düşünün.
Birincil anahtar: customer_id
| customer_id | name |
|---|---|
| 1 | Ava |
| 2 | Ben |
| 3 | Chen |
| 4 | Dia |
Birincil anahtar: order_id
Yabancı anahtar: customer_id → customers.customer_id
| order_id | customer_id | order_total |
|---|---|---|
| 101 | 1 | 50 |
| 102 | 1 | 120 |
| 103 | 2 | 35 |
| 104 | 5 | 70 |
Dikkat edin: order_id = 104, customer_id = 5 referans ediyor; bu customers içinde yok. Bu "eşleşmeyen" örnek, LEFT JOIN, RIGHT JOIN ve FULL OUTER JOIN davranışlarını görmek için faydalıdır.
Birincil anahtar: payment_id
Yabancı anahtar: order_id → orders.order_id
| payment_id | order_id | amount |
|---|---|---|
| 9001 | 101 | 50 |
| 9002 | 102 | 60 |
| 9003 | 102 | 60 |
| 9004 | 999 | 25 |
İki önemli öğretici detay:
order_id = 102 iki ödeme satırına sahip (bölünmüş ödeme). orders ile payments birleştirildiğinde o sipariş iki kez görünecektir—işte burada çoğaltmalar insanları şaşırtır.payment_id = 9004, order_id = 999 referans ediyor; bu orders içinde yok. Bu da başka bir "eşleşmeyen" durum oluşturur.orders ile payments birleştirildiğinde 102 numaralı sipariş iki kez tekrarlanır.INNER JOIN, yalnızca her iki tabloda da eşleşme olan satırları döndürür. Bir müşterinin siparişi yoksa sonuçta görünmez. Bir sipariş var ama referans verdiği müşteri yoksa (kötü veri), o sipariş de görünmez.
Bir "sol" tablo seçersiniz, bir "sağ" tabloyu birleştirirsiniz ve ON koşulunda nasıl ilişkilendirileceğini söylersiniz.
SELECT
c.customer_id,
c.name,
o.order_id,
o.order_date
FROM customers c
INNER JOIN orders o
ON o.customer_id = c.customer_id;
Ana fikir ON o.customer_id = c.customer_id satırıdır: SQL'e satırların nasıl eşleşeceğini söyler.
Sadece en az bir sipariş vermiş müşterileri (ve sipariş detaylarını) listelemek istiyorsanız, INNER JOIN doğal seçimdir:
SELECT
c.name,
o.order_id,
o.total_amount
FROM customers c
INNER JOIN orders o
ON o.customer_id = c.customer_id
ORDER BY o.order_id;
Bu, "sipariş takip e-postası gönder" veya "satış başına müşteri geliri hesapla" gibi kullanım durumları için uygundur (sadece satın alma yapan müşterilerle ilgileniyorsanız).
Join yazıp ON koşulunu unutursanız (veya yanlış sütunla eşleştirirseniz), istemeden Kartezyen çarpımı (her müşteri ile her sipariş) oluşturabilir veya incelikli şekilde yanlış eşlemeler üretebilirsiniz.
Kötü (bunu yapmayın):
SELECT c.name, o.order_id
FROM customers c
JOIN orders o;
Her zaman ON (veya özel durumlarda USING) ile net bir join koşulu yazdığınızdan emin olun.
LEFT JOIN, sol tablodaki tüm satırları döndürür ve sağ tablodan eşleşenleri ekler; eşleşme yoksa sağ taraf sütunları NULL olur.
Ana tablodan eksiksiz bir liste istiyorsanız ve ilişkili veriler opsiyonelse LEFT JOIN kullanın.
Örnek: "Tüm müşterileri gösterin ve varsa siparişlerini ekleyin."
SELECT
c.customer_id,
c.name,
o.order_id,
o.order_date
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
ORDER BY c.customer_id;
o.order_id gibi orders sütunları NULL olur.Çok yaygın bir kullanım, ilgili kaydı olmayan öğeleri bulmaktır.
Örnek: "Hiç sipariş vermemiş müşteriler kimler?"
SELECT
c.customer_id,
c.name
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
WHERE o.order_id IS NULL;
WHERE ... IS NULL koşulu, join eşleşmesi bulunamayan sol-tablo satırlarını tutar.
LEFT JOIN, sağ tarafta birden çok eşleşme olduğunda sol tablo satırlarını "çoğaltabilir".
Bir müşterinin 3 siparişi varsa, o müşteri 3 kez görünür—her sipariş için bir satır. Bu beklenen bir davranıştır ama müşteri saymak gibi durumlarda şaşırtıcı olabilir.
Örneğin, bu sorgu siparişleri sayar (müşterileri değil):
SELECT COUNT(*)
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id;
Müşteri saymak istiyorsanız genellikle anahtarın sayısını sayarsınız (ör. COUNT(DISTINCT c.customer_id)), ölçtüğünüz şeye bağlı olarak.
RIGHT JOIN, sağ tablodaki tüm satırları tutar ve sadece eşleşen sol tablodaki satırları ekler. Eşleşme yoksa sol tablonun sütunları NULL olur. Aslında LEFT JOIN'in tersidir.
Örnek tablolarımızı kullanarak, her ödemeyi listelemek isteyebileceğinizi düşünün; ödeme bir siparişe bağlanamıyor olabilir (sipariş silinmiş olabilir veya ödeme verisi karışıksa).
SELECT
o.order_id,
o.customer_id,
p.payment_id,
p.amount,
p.paid_at
FROM orders o
RIGHT JOIN payments p
ON o.order_id = p.order_id;
Sonuç:
payments sağ tarafta).o.order_id ve o.customer_id NULL olur.Çoğu zaman RIGHT JOIN, tablo sırasını değiştirip LEFT JOIN ile yeniden yazılabilir:
SELECT
o.order_id,
o.customer_id,
p.payment_id,
p.amount,
p.paid_at
FROM payments p
LEFT JOIN orders o
ON o.order_id = p.order_id;
Aynı sonucu döndürür; ancak birçok kişi için okunması daha kolaydır: önce ilgilendiğiniz "ana" tabloyu (burada payments) başlatırsınız ve sonra isteğe bağlı veriyi çekersiniz.
Çoğu SQL stil kılavuzu RIGHT JOIN kullanımını tavsiye etmez çünkü okuyan kişinin mantığı tersine çevirmesini gerektirir. Bir standart haline getirilen yaklaşım:
Opsiyonel ilişkiler her zaman LEFT JOIN ile yazıldığında sorgular taraması daha kolay olur.
Uzun bir sorguyu düzenlerken, "tutulması gereken" tablo sağda ise tüm sorguyu tersine çevirmek yerine tek bir join'i RIGHT JOIN yapmak hızlı ve düşük riskli bir değişiklik olabilir.
FULL OUTER JOIN, her iki tablodaki tüm satırları döndürür.
NULL olacak şekilde yine görünür.NULL olacak şekilde görünür.Klasik bir kullanım, siparişler vs ödemeler mutabakatıdır:
Örnek:
SELECT
o.order_id,
o.customer_id,
p.payment_id,
p.amount
FROM orders o
FULL OUTER JOIN payments p
ON p.order_id = o.order_id;
FULL OUTER JOIN, PostgreSQL, SQL Server ve Oracle tarafından desteklenir.
MySQL ve SQLite'ta doğrudan yoktur; bir çözüm gerekir.
Veritabanınız FULL OUTER JOIN desteklemiyorsa, bunu şu şekilde taklit edebilirsiniz:
orders tablosunun tüm satırlarını (mevcutsa eşleşen ödemelerle) alın,payments tablosunda eşleşmeyen satırları ekleyin.Yaygın bir kalıp:
SELECT o.order_id, o.customer_id, p.payment_id, p.amount
FROM orders o
LEFT JOIN payments p
ON p.order_id = o.order_id
UNION
SELECT o.order_id, o.customer_id, p.payment_id, p.amount
FROM orders o
RIGHT JOIN payments p
ON p.order_id = o.order_id;
İpucu: bir tarafta NULL gördüğünüzde, o satırın diğer tabloda eksik olduğunu anlarsınız—mutabakat ve denetimler için tam aradığınız şey budur.
CROSS JOIN, iki tablonun her olası eşleşmesini döndürür. Tablo A 3 satır, tablo B 4 satırsa sonuç 3 × 4 = 12 satır olur. Buna Kartezyen çarpım denir.
Kulağa korkutucu geliyor—ve olabilir—ama kombinasyon üretmek istediğinizde gerçekten faydalıdır.
Ürün seçeneklerini ayrı tablolarda tuttuğunuzu düşünün:
sizes: S, M, Lcolors: Red, BlueCROSS JOIN tüm varyantları üretmek için kullanılabilir:
SELECT
s.size,
c.color
FROM sizes AS s
CROSS JOIN colors AS c;
Sonuç (3 × 2 = 6 satır):
Satır sayıları çarpıldığından CROSS JOIN hızla patlayabilir:
Bu sorguları yavaşlatabilir, belleği zorlayabilir ve kimsenin kullanamayacağı bir çıktı üretebilir. Kombinasyonlara ihtiyacınız varsa, giriş tablolarını küçük tutun veya kontrollü filtreler ekleyin.
SELF JOIN, adından da anlaşılacağı gibi bir tabloyu kendisiyle birleştirmektir. Bir satırın aynı tablodaki başka bir satırla ilişkili olduğu durumlarda faydalıdır—en yaygın örnek çalışanlar ve yöneticiler hiyerarşisidir.
Aynı tabloyu iki kez kullandığınız için her "kopyaya" farklı bir alias vermelisiniz. Alias'lar sorguyu okunur kılar ve SQL'e hangi tarafı kastettiğinizi söyler.
Yaygın bir kalıp:
e çalışan içinm yönetici içinemployees tablosu şu kolonlara sahip olsun:
idnamemanager_id (başka bir çalışanın id'sine işaret eder)Her çalışanı yönetici adıyla listelemek için:
SELECT
e.id,
e.name AS employee_name,
m.name AS manager_name
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.id;
Sorgu LEFT JOIN kullanır, INNER JOIN değil. Çünkü bazı çalışanların (ör. CEO) yöneticisi olmayabilir; bu durumda manager_id genellikle NULL olur ve LEFT JOIN çalışan satırını tutup manager_name'i NULL gösterir.
INNER JOIN kullansaydınız, üst düzey yöneticiler sonuçtan kaybolurdu çünkü eşleşecek bir yönetici satırı yoktur.
Bir JOIN, iki tablonun nasıl ilişkilendiğini bilmez—bunu siz belirtmelisiniz. Bu ilişki join koşulunda tanımlanır ve JOIN'in yanında yer almalıdır çünkü bu, tabloların nasıl eşleştiğini açıklar, nihai filtreleme değil.
ON: en esnek ve en yaygın olanFarklı sütun isimleri, birden çok koşul veya ek kurallar gerektiğinde ON kullanın.
SELECT
c.customer_id,
c.name,
o.order_id,
o.created_at
FROM customers AS c
INNER JOIN orders AS o
ON o.customer_id = c.customer_id;
ON ile iki sütunda eşleştirme veya daha karmaşık mantık tanımlayabilirsiniz.
USING: sadece aynı isimli sütunlar için kısa yolBazı veritabanları (PostgreSQL, MySQL) USING desteği sunar. Her iki tabloda aynı ada sahip bir sütun varsa ve sadece bu sütunla eşlemek istiyorsanız kısaltma sağlar.
SELECT
customer_id,
name,
order_id
FROM customers
JOIN orders
USING (customer_id);
USING genelde çıktıdan tek bir customer_id sütunu döndürme avantajı sağlar (iki kopya yerine).
Join sonrası sütun isimleri çakışabilir (id, created_at, status). SELECT id yazarsanız veritabanı "ambiguous column" hatası verebilir—ya da yanlış id'yi okuyabilirsiniz.
Açıklık için tablo önekleri veya alias kullanın:
SELECT c.customer_id, o.order_id
FROM customers AS c
JOIN orders AS o
ON o.customer_id = c.customer_id;
SELECT * kullanmaktan kaçınınSELECT * joinlerle hızla karmaşıklaşır: gereksiz sütunları çekersiniz, isim çakışmaları olur ve sorgunun amacı zor anlaşılır. Bunun yerine ihtiyacınız olan sütunları seçin—sonuç daha temiz, bakımı daha kolay ve genelde daha verimlidir.
JOIN yaparken, WHERE ve ON her ikisi de "filtre" uygular ama farklı zamanlarda.
Bu zamanlama farkı, insanların LEFT JOIN'i yanlışlıkla INNER JOIN gibi davranır hale getirmesine neden olur.
Tüm müşterileri, hatta yakın zamanda ödenmiş siparişi olmayanları istiyorsunuz diyelim.
SELECT c.customer_id, c.name, o.order_id, o.status, o.order_date
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
WHERE o.status = 'PAID'
AND o.order_date >= DATE '2025-01-01';
Sorun: siparişi olmayan müşteriler için o.status ve o.order_date NULL olur. WHERE koşulu bu satırları eler; böylece LEFT JOIN etkide INNER JOIN gibi davranır.
SELECT c.customer_id, c.name, o.order_id, o.status, o.order_date
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
AND o.status = 'PAID'
AND o.order_date >= DATE '2025-01-01';
Böylece nitelikli siparişi olmayan müşteriler yine görünür (sipariş sütunları NULL olur), ki genelde LEFT JOIN kullanmanın amacı budur.
WHERE o.order_id IS NOT NULL) kullanın.Joinler sadece sütun eklemez—satırları da çoğaltabilir. Bu genelde doğru davranıştır ama toplamlar aniden iki katına çıkınca sürpriz yaratabilir.
Bir join, her eşleşen satır çifti için bir çıktı satırı üretir.
customersı orders ile birleştirirseniz her müşteri sipariş başına birden fazla kez görünebilir.order_items gibi başka bir "çok" tablonuz varsa, payments × items etkisiyle beklenmedik çarpımlar oluşur.Eğer hedefiniz "müşteri başına bir satır" veya "sipariş başına bir satır" ise, "çok" tarafı önce özetleyin, sonra join yapın.
-- Ödemelerden sipariş başına bir satır
WITH payment_totals AS (
SELECT
order_id,
SUM(amount) AS total_paid,
COUNT(*) AS payment_count
FROM payments
GROUP BY order_id
)
SELECT
o.order_id,
o.customer_id,
COALESCE(pt.total_paid, 0) AS total_paid,
COALESCE(pt.payment_count, 0) AS payment_count
FROM orders o
LEFT JOIN payment_totals pt
ON pt.order_id = o.order_id;
Bu, join şeklinin tahmin edilebilir olmasını sağlar: bir sipariş satırı bir sipariş satırı olarak kalır.
SELECT DISTINCT, çoğaltmaları görünürde düzeltir ama gerçek problemi gizleyebilir:
Sadece çoğaltmaların tamamen kazara olduğunu ve nedenini bildiğinizde kullanın.
Sonuca güvenmeden önce satır sayılarınızı karşılaştırın:
JOIN'ler genelde "yavaş sorguların" suçlusu ilan edilir ama gerçek sebep çoğunlukla veritabanına ne kadar veri birleştirmesini istediğiniz ve eşleşen satırları ne kadar hızlı bulabildiği ile ilgilidir.
Bir indeksi kitap içindeki içindekiler tablosu gibi düşünün. İndeks yoksa veritabanı JOIN koşulunu sağlamak için çok sayıda satırı taramak zorunda kalabilir. JOIN anahtarında (ör. customers.customer_id ve orders.customer_id) indeks varsa veritabanı ilgili satırlara çok daha hızlı atlayabilir.
Nasıl kullanacağınızı bilmek için iç detaylara hakim olmanız gerekmez: eğer bir sütun sık sık eşleşme için kullanılıyorsa indekse uygun bir adaydır.
Mümkün olduğunda, kararlı, tekil tanımlayıcılar üzerinde join yapın:
customers.customer_id = orders.customer_idcustomers.email = orders.email veya customers.name = orders.nameİsimler değişebilir ve tekrar edebilir. E-postalar değişebilir, eksik olabilir veya format/farklılık sorunları olabilir. ID'ler tutarlı eşleme için tasarlanmıştır ve genelde indekslidir.
İki alışkanlık JOIN'leri belirgin şekilde hızlandırır:
SELECT *'den kaçının—gereksiz sütunlar bellek ve ağ kullanımını artırır.Örnek: önce siparişleri sınırlayıp sonra join yapın:
SELECT c.customer_id, c.name, o.order_id, o.created_at
FROM customers c
JOIN (
SELECT order_id, customer_id, created_at
FROM orders
WHERE created_at >= DATE '2025-01-01'
) o
ON o.customer_id = c.customer_id;
Eğer bu sorgularla bir uygulama oluşturuyorsanız (ör. PostgreSQL destekli bir raporlama sayfası), Koder.ai gibi araçlar şemayı, uç noktaları ve UI'ı hızla oluşturmanıza yardımcı olabilir—ama doğruyu belirleyen JOIN mantığını siz kontrol etmelisiniz.
NULL)NULL ile)NULLBir SQL JOIN, iki (veya daha fazla) tablonun ilişkili sütunları üzerinden eşleşen satırlarını tek bir sonuç kümesinde birleştirir—çoğunlukla birincil anahtar ile yabancı anahtar arasındaki eşleme (customers.customer_id = orders.customer_id gibi). Normalleştirilmiş tabloları raporlar, denetimler veya analizler için yeniden bağlamanın yoludur.
Sadece her iki tabloda da ilişki bulunan satırları istediğinizde INNER JOIN kullanın.
Onaylanmış ilişkileri göstermek için idealdir; örneğin sadece gerçekten sipariş veren müşterileri listelemek gibi.
LEFT JOIN ana (sol) tablodan tüm satırları alır ve sağ tablodan eşleşenleri ekler.
“Eşleşmesi olmayan” kayıtları bulmak için şu yaygın kalıbı kullanın:
SELECT c.customer_id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE o.order_id IS ;
RIGHT JOIN, sağ tablodaki tüm satırları tutar ve sol tablodan sadece eşleşenleri ekler. Birçok ekip bunu geriye doğru okunduğu için tercih etmez.
Çoğu durumda, tablo sırasını değiştirip LEFT JOIN kullanarak aynı sonucu elde edebilirsiniz:
FROM payments p
LEFT JOIN orders o ON o.order_id = p.order_id
FULL OUTER JOIN, hem sol hem sağ tablodaki tüm satırları döndürmek için kullanılır: eşleşenler birleşir, tek tarafta olanlar diğer sütunlarda NULL gösterir.
Mutabakatlar için idealdir: "ödeme olan siparişler", "ödemesiz siparişler" ve "siparişi olmayan ödemeler" gibi tüm durumlardan aynı anda haberdar olmak istediğinizde kullanırsınız.
Bazı veritabanları (özellikle MySQL ve SQLite) FULL OUTER JOIN'i doğrudan desteklemez. Yaygın bir çözüm, iki sorguyu birleştirmektir:
orders LEFT JOIN paymentsGenelde bu UNION ile yapılır (veya dikkatle UNION ALL).
CROSS JOIN, iki tablo arasındaki tüm kombinasyonları döndürür (Kartezyen çarpım). Senaryolar oluşturmak (ör. beden × renk) veya test verisi üretmek için kullanışlıdır.
Dikkat: satır sayısı hızla çarpılır; girdiler küçük ve kontrollü değilse çıktı patlayabilir.
Self join, bir tablonun kendisiyle ilişkilendirildiği durumdur; genellikle çalışan → yönetici gibi hiyerarşiler için kullanılır.
Aynı tabloyu iki kez kullandığınız için her kopyaya farklı takma ad (alias) vermeniz gerekir:
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.id
ON, tabloların nasıl eşleşeceğini tanımlar; WHERE ise birleşim sonrası nihai sonucu filtreler. LEFT JOIN ile sağ tabloya ait bir koşulu WHERE içine koymak, NULL eşleşmeleri eler ve etkide INNER JOIN gibi davranır.
Eğer sol tablodaki tüm satırları tutmak ama sağ tablodan sadece belirli satırların eşleşmesini istiyorsanız, sağ-tablo koşullarını içine koyun.
Joinler, birden-çoğa veya çoğa-çoğa ilişkilerde satırları çoğaltabilir. Örneğin, bir siparişin iki ödemesi varsa orders ile payments birleştirildiğinde o sipariş iki kez görünür.
"Satır başına bir" sonuç istiyorsanız, çoklu tarafı önce gruplayıp topladıktan (SUM, COUNT) sonra join yapın. DISTINCT son çare olmalı; gerçek problemi gizleyebilir ve toplamları bozabilir.
Bu, hiç sipariş vermemiş müşterileri bulur.
ON