MariaDB ve MySQL’de LEFT JOIN Kullanımı ve Farkı

Veritabanı sorgularında en sık karşılaşılan sorunlardan biri, iki veya daha fazla tabloyu birleştirirken hangi kayıtların döneceğini tam olarak anlayamamaktır. Özellikle production ortamında yanlış JOIN türü kullanmak, eksik veya fazla veri çekmene, dolayısıyla uygulamanın hatalı çalışmasına yol açar. Bu yazıda MariaDB ve MySQL üzerinde LEFT JOIN kullanımını, INNER JOIN ile farkını ve gerçek dünya senaryolarıyla nasıl uygulandığını ele alacağız.

JOIN Nedir, Neden Gereklidir?

İlişkisel veritabanlarında veriler normalizasyon gereği birden fazla tabloya dağıtılır. Müşteri bilgileri ayrı tabloda, siparişler ayrı tabloda, ürünler ayrı tabloda durur. Bu tabloları birbirine bağlamak için JOIN kullanırız. Ama her JOIN türü aynı sonucu vermez ve bu farkı bilmeden yazdığın sorgular seni ciddi şekilde yanıltabilir.

Temel JOIN türleri şunlardır:

  • INNER JOIN: Her iki tabloda da eşleşen kayıtları getirir
  • LEFT JOIN (LEFT OUTER JOIN): Sol tablodaki tüm kayıtları getirir, sağ tabloda eşleşme olmasa bile
  • RIGHT JOIN (RIGHT OUTER JOIN): Sağ tablodaki tüm kayıtları getirir, sol tabloda eşleşme olmasa bile
  • FULL OUTER JOIN: Her iki tablodaki tüm kayıtları getirir (MySQL/MariaDB’de doğrudan desteklenmez, UNION ile simüle edilir)

Bu yazıda odak noktamız LEFT JOIN olacak çünkü pratikte en çok ihtiyaç duyulan ve en çok karıştırılan JOIN türü odur.

Test Ortamı Kurulumu

Örnekleri takip edebilmek için önce test veritabanımızı ve tablolarımızı oluşturalım. Gerçek bir e-ticaret senaryosu üzerinden gideceğiz.

mysql -u root -p

CREATE DATABASE eticaret_test;
USE eticaret_test;

-- Müşteriler tablosu
CREATE TABLE musteriler (
    id INT AUTO_INCREMENT PRIMARY KEY,
    ad VARCHAR(100) NOT NULL,
    email VARCHAR(150) UNIQUE,
    kayit_tarihi DATE,
    sehir VARCHAR(50)
);

-- Siparişler tablosu
CREATE TABLE siparisler (
    id INT AUTO_INCREMENT PRIMARY KEY,
    musteri_id INT,
    urun_adi VARCHAR(200),
    tutar DECIMAL(10,2),
    siparis_tarihi DATETIME,
    durum VARCHAR(30),
    FOREIGN KEY (musteri_id) REFERENCES musteriler(id)
);

-- Kategoriler tablosu
CREATE TABLE kategoriler (
    id INT AUTO_INCREMENT PRIMARY KEY,
    kategori_adi VARCHAR(100),
    ust_kategori_id INT NULL
);

-- Ürünler tablosu
CREATE TABLE urunler (
    id INT AUTO_INCREMENT PRIMARY KEY,
    urun_adi VARCHAR(200),
    kategori_id INT,
    fiyat DECIMAL(10,2),
    stok INT DEFAULT 0,
    FOREIGN KEY (kategori_id) REFERENCES kategoriler(id)
);

Şimdi test verilerini ekleyelim:

-- Müşteri verileri
INSERT INTO musteriler (ad, email, kayit_tarihi, sehir) VALUES
('Ahmet Yılmaz', '[email protected]', '2023-01-15', 'İstanbul'),
('Fatma Demir', '[email protected]', '2023-02-20', 'Ankara'),
('Mehmet Kaya', '[email protected]', '2023-03-10', 'İzmir'),
('Zeynep Çelik', '[email protected]', '2023-04-05', 'Bursa'),
('Ali Şahin', '[email protected]', '2023-05-12', 'Antalya'),
('Elif Öztürk', '[email protected]', '2023-06-18', 'İstanbul');

-- Sipariş verileri (Ali ve Elif hiç sipariş vermedi)
INSERT INTO siparisler (musteri_id, urun_adi, tutar, siparis_tarihi, durum) VALUES
(1, 'Laptop', 15000.00, '2023-07-01 10:30:00', 'tamamlandi'),
(1, 'Mouse', 250.00, '2023-07-15 14:20:00', 'tamamlandi'),
(2, 'Klavye', 500.00, '2023-08-01 09:00:00', 'tamamlandi'),
(3, 'Monitör', 8000.00, '2023-08-20 16:45:00', 'iptal'),
(4, 'Kulaklık', 1200.00, '2023-09-05 11:15:00', 'beklemede');

-- Kategori verileri
INSERT INTO kategoriler (kategori_adi, ust_kategori_id) VALUES
('Elektronik', NULL),
('Bilgisayar', 1),
('Ses Sistemleri', 1),
('Giyim', NULL),
('Spor', NULL);

-- Ürün verileri (Spor kategorisinde ürün yok)
INSERT INTO urunler (urun_adi, kategori_id, fiyat, stok) VALUES
('Gaming Laptop', 2, 25000.00, 10),
('Ofis Bilgisayarı', 2, 12000.00, 25),
('Bluetooth Kulaklık', 3, 1500.00, 50),
('T-Shirt', 4, 150.00, 200);

LEFT JOIN Sözdizimi ve Temel Kullanım

LEFT JOIN sözdizimi şu şekildedir:

SELECT
    sol_tablo.kolon1,
    sol_tablo.kolon2,
    sag_tablo.kolon1
FROM sol_tablo
LEFT JOIN sag_tablo ON sol_tablo.id = sag_tablo.sol_tablo_id;

LEFT JOIN’in temel mantığı şudur: Sol tablodaki (FROM’dan sonra gelen tablo) her satırı döndür. Sağ tabloda eşleşen satır varsa onun kolonlarını da ekle. Eşleşme yoksa sağ tablonun kolonlarını NULL olarak göster.

Şimdi bunu pratikte görelim. Tüm müşterileri ve varsa siparişlerini listeleyelim:

SELECT
    m.id AS musteri_id,
    m.ad AS musteri_adi,
    m.sehir,
    s.id AS siparis_id,
    s.urun_adi,
    s.tutar,
    s.durum
FROM musteriler m
LEFT JOIN siparisler s ON m.id = s.musteri_id
ORDER BY m.id;

Bu sorgunun çıktısında 6 müşterinin tamamını görürsün. Ahmet’in 2 siparişi olduğu için 2 satırda görünür. Ali ve Elif hiç sipariş vermediği için sipariş kolonları NULL olarak gelir. İşte LEFT JOIN’in INNER JOIN’den temel farkı burada ortaya çıkar. INNER JOIN kullansaydın Ali ve Elif’i hiç göremezdin.

INNER JOIN ile Karşılaştırma

Aynı sorguyu INNER JOIN ile çalıştıralım ve farkı net görelim:

-- INNER JOIN: Sadece siparişi olan müşteriler gelir
SELECT
    m.id AS musteri_id,
    m.ad AS musteri_adi,
    s.id AS siparis_id,
    s.urun_adi,
    s.tutar
FROM musteriler m
INNER JOIN siparisler s ON m.id = s.musteri_id
ORDER BY m.id;

-- LEFT JOIN: Siparişi olmayan müşteriler de gelir
SELECT
    m.id AS musteri_id,
    m.ad AS musteri_adi,
    s.id AS siparis_id,
    s.urun_adi,
    s.tutar
FROM musteriler m
LEFT JOIN siparisler s ON m.id = s.musteri_id
ORDER BY m.id;

INNER JOIN 5 satır döndürür (Ahmet iki kez görünür). LEFT JOIN ise 7 satır döndürür çünkü Ali ve Elif de NULL değerlerle listelenir.

Ne zaman hangisini kullanmalısın:

  • INNER JOIN kullan: Sadece her iki tabloda da eşleşen verilere ihtiyacın varsa. Örneğin “sipariş vermiş müşterilerin bilgilerini getir”
  • LEFT JOIN kullan: Ana tablodaki tüm kayıtları görmek istiyorsan ve bağlantılı tablodaki verinin olup olmaması fark etmiyorsa. Örneğin “tüm müşterileri ve varsa siparişlerini getir”

Gerçek Dünya Senaryosu 1: Hiç Sipariş Vermemiş Müşteriler

E-ticaret sistemlerinde klasik bir senaryo: Kayıt olmuş ama hiç alışveriş yapmamış müşterileri bul. Bunları pazarlama departmanına ileteceksin.

SELECT
    m.id,
    m.ad,
    m.email,
    m.kayit_tarihi,
    m.sehir,
    COUNT(s.id) AS toplam_siparis
FROM musteriler m
LEFT JOIN siparisler s ON m.id = s.musteri_id
GROUP BY m.id, m.ad, m.email, m.kayit_tarihi, m.sehir
HAVING COUNT(s.id) = 0
ORDER BY m.kayit_tarihi;

Bu sorgunun mantığı şudur: LEFT JOIN ile tüm müşterileri çek, siparişleri say, sadece sipariş sayısı 0 olanları filtrele. INNER JOIN kullansaydın bu sorgu hiçbir zaman çalışmazdı çünkü eşleşmeyen kayıtlar zaten gelmezdi.

Benzer şekilde en az 1 siparişi olan ve toplam harcaması belirli bir değerin üzerindeki müşterileri bul:

SELECT
    m.id,
    m.ad,
    m.email,
    COUNT(s.id) AS siparis_sayisi,
    COALESCE(SUM(s.tutar), 0) AS toplam_harcama
FROM musteriler m
LEFT JOIN siparisler s ON m.id = s.musteri_id AND s.durum = 'tamamlandi'
GROUP BY m.id, m.ad, m.email
HAVING toplam_harcama > 5000
ORDER BY toplam_harcama DESC;

Burada dikkat edilmesi gereken önemli nokta: JOIN koşuluna s.durum = 'tamamlandi' ekledik. Bu koşul WHERE’e değil ON’a yazıldı. Bu fark çok önemlidir. WHERE’e yazsaydın LEFT JOIN davranışı bozulurdu, NULL olan satırlar filtrelenip giderdi ve sorgun INNER JOIN gibi çalışırdı.

LEFT JOIN’de WHERE Tuzağı

Bu konuyu ayrıca vurgulamak lazım çünkü çok sık yapılan bir hatadır:

-- YANLIS: Bu sorgu aslında INNER JOIN gibi davranır
SELECT m.ad, s.urun_adi
FROM musteriler m
LEFT JOIN siparisler s ON m.id = s.musteri_id
WHERE s.durum = 'tamamlandi';
-- Ali ve Elif burada gözükmez, çünkü s.durum NULL olunca WHERE filtresi onları eliyor

-- DOGRU: Koşulu ON içine yaz
SELECT m.ad, s.urun_adi
FROM musteriler m
LEFT JOIN siparisler s ON m.id = s.musteri_id AND s.durum = 'tamamlandi';
-- Ali ve Elif NULL değerleriyle listelenir

-- DOGRU ALTERNATIF: NULL kontrolü ekle
SELECT m.ad, s.urun_adi
FROM musteriler m
LEFT JOIN siparisler s ON m.id = s.musteri_id
WHERE s.durum = 'tamamlandi' OR s.id IS NULL;

Prodüksiyon ortamında bu tuzağa düşen sorgular genellikle fark edilmez çünkü sorgu hata vermez, sadece yanlış sonuç döndürür. Bu yüzden LEFT JOIN kullanırken sağ tabloya ait filtre koşullarını nereye yazdığını çok dikkatli kontrol et.

Gerçek Dünya Senaryosu 2: Kategori-Ürün Raporlama

Bir içerik yönetim sistemi veya e-ticaret paneli düşün. Tüm kategorileri listeleyen ve her kategorideki ürün sayısını gösteren bir rapor isteniyor. Ürün olmayan kategoriler de görünmeli.

SELECT
    k.id AS kategori_id,
    k.kategori_adi,
    COUNT(u.id) AS urun_sayisi,
    COALESCE(AVG(u.fiyat), 0) AS ortalama_fiyat,
    COALESCE(SUM(u.stok), 0) AS toplam_stok
FROM kategoriler k
LEFT JOIN urunler u ON k.id = u.kategori_id
GROUP BY k.id, k.kategori_adi
ORDER BY urun_sayisi DESC;

Bu sorgu Spor kategorisini de döndürür, ürün sayısı 0 olarak. INNER JOIN kullansaydın Spor kategorisi raporda hiç görünmezdi ve müdürün “Spor kategorisi nerede?” diye sorunca ekibin başı ağrırdı.

Gerçek Dünya Senaryosu 3: Çoklu LEFT JOIN

Bazen birden fazla tabloyu LEFT JOIN ile bağlaman gerekir. Sipariş bazında müşteri ve ürün detaylarını getiren kapsamlı bir rapor:

SELECT
    m.ad AS musteri_adi,
    m.sehir,
    s.id AS siparis_no,
    s.urun_adi,
    s.tutar,
    s.durum,
    s.siparis_tarihi,
    CASE
        WHEN s.id IS NULL THEN 'Sipariş Yok'
        WHEN s.durum = 'tamamlandi' THEN 'Tamamlandı'
        WHEN s.durum = 'iptal' THEN 'İptal Edildi'
        WHEN s.durum = 'beklemede' THEN 'Beklemede'
        ELSE 'Bilinmiyor'
    END AS durum_etiketi
FROM musteriler m
LEFT JOIN siparisler s ON m.id = s.musteri_id
ORDER BY m.id, s.siparis_tarihi;

Çoklu tablo senaryosu için kategorilerle birlikte ürün ve sipariş detayları:

SELECT
    k.kategori_adi,
    u.urun_adi,
    u.fiyat,
    COUNT(s.id) AS satis_adedi,
    COALESCE(SUM(s.tutar), 0) AS toplam_ciro
FROM kategoriler k
LEFT JOIN urunler u ON k.id = u.kategori_id
LEFT JOIN siparisler s ON u.urun_adi = s.urun_adi AND s.durum = 'tamamlandi'
GROUP BY k.kategori_adi, u.urun_adi, u.fiyat
ORDER BY toplam_ciro DESC;

Çoklu LEFT JOIN kullanırken dikkat et: Her yeni LEFT JOIN bir önceki sonuç kümesine eklenir. Zincirleme JOIN’lerde sıralama ve hangi tablonun “sol” hangisinin “sağ” olduğu çıktıyı doğrudan etkiler.

Performans Konuları

LEFT JOIN kullanırken performansı etkileyen birkaç kritik faktör var. Bunları bilmeden production’da büyük tablolarla çalışmak ciddi sorunlara yol açabilir.

İndeks kullanımı hayati önem taşır. JOIN koşullarında kullanılan kolonlar mutlaka indekslenmiş olmalı:

-- JOIN performansını kontrol et
EXPLAIN SELECT
    m.ad,
    s.urun_adi,
    s.tutar
FROM musteriler m
LEFT JOIN siparisler s ON m.id = s.musteri_id
WHERE m.sehir = 'İstanbul';

-- Gerekli indeksleri ekle
CREATE INDEX idx_siparisler_musteri_id ON siparisler(musteri_id);
CREATE INDEX idx_musteriler_sehir ON musteriler(sehir);
CREATE INDEX idx_siparisler_durum ON siparisler(durum);

-- İndeks sonrası performansı tekrar kontrol et
EXPLAIN SELECT
    m.ad,
    s.urun_adi,
    s.tutar
FROM musteriler m
LEFT JOIN siparisler s ON m.id = s.musteri_id
WHERE m.sehir = 'İstanbul';

EXPLAIN çıktısında type kolonuna bak. ALL görüyorsan full table scan yapılıyor demektir, bu büyük tablolarda felakettir. ref veya eq_ref görmen gerekir.

Büyük veri setlerinde LEFT JOIN optimizasyonu için pratik ipuçları:

  • SELECT * kullanma, sadece ihtiyacın olan kolonları seç
  • WHERE koşullarını mümkün olduğunca sol tabloya (drive table) uygula, böylece JOIN öncesi veri kümesi küçülür
  • Çok fazla LEFT JOIN zincirliyorsan sorguyu alt sorgulara veya geçici tablolara böl
  • MariaDB’de optimizer_switch ayarlarını inceleyerek JOIN optimizasyonunu etkileyebilirsin
-- Sorgu maliyetini ve süresini izlemek için
SET profiling = 1;

SELECT
    m.ad,
    COUNT(s.id) AS siparis_sayisi
FROM musteriler m
LEFT JOIN siparisler s ON m.id = s.musteri_id
GROUP BY m.id, m.ad;

SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;

NULL Değerlerle Çalışmak

LEFT JOIN’in en çok yanılgı yaratan yanlarından biri NULL değer yönetimidir. Sağ tabloda eşleşme olmayan satırlarda tüm kolonlar NULL gelir. Bu durumu düzgün yönetmezsen uygulama katmanında beklenmedik hatalar alırsın.

-- COALESCE ile NULL değerleri varsayılan değerle değiştir
SELECT
    m.ad,
    COALESCE(COUNT(s.id), 0) AS siparis_sayisi,
    COALESCE(SUM(s.tutar), 0.00) AS toplam_tutar,
    COALESCE(MAX(s.siparis_tarihi), 'Hiç sipariş yok') AS son_siparis
FROM musteriler m
LEFT JOIN siparisler s ON m.id = s.musteri_id
GROUP BY m.id, m.ad;

-- IS NULL ile sadece eşleşmeyenleri bul (Anti-JOIN pattern)
SELECT m.id, m.ad, m.email
FROM musteriler m
LEFT JOIN siparisler s ON m.id = s.musteri_id
WHERE s.id IS NULL;
-- Bu sorgu NOT IN veya NOT EXISTS alternatifine göre çoğu zaman daha hızlıdır

Anti-JOIN pattern’i sysadmin bakış açısıyla çok işlevlidir. Log tablosu ile kullanıcı tablosunu birleştirip belirli bir tarihten sonra hiç log girişi olmayan kullanıcıları bulmak, erişilmemiş dosya kayıtlarını tespit etmek gibi güvenlik ve denetim sorgularında bu pattern sıklıkla kullanılır.

Özet ve Pratik Kural Kartı

LEFT JOIN’i doğru kullanmak için aklında tutman gereken kurallar:

  • Sol tablo her zaman tam gelir. Sol tablodaki hiçbir satır kaybolmaz
  • Sağ tabloda eşleşme yoksa NULL gelir. Bunu COALESCE veya IFNULL ile yönet
  • Filtre koşullarını doğru yere yaz. Sağ tabloya ait filtreler ON içine girilmeli, WHERE’e girerse davranış INNER JOIN’e döner
  • JOIN kolonlarını indeksle. Özellikle büyük tablolarda bu şart
  • EXPLAIN kullan. Sorgunun nasıl çalıştığını görmeden prodüksiyona alma
  • Anti-JOIN için LEFT JOIN + IS NULL kombinasyonu güçlüdür. NOT IN’den genellikle daha verimlidir

Sonuç

LEFT JOIN, veritabanı sorgularının temel yapı taşlarından biridir ve doğru kullanıldığında raporlama, veri analizi ve uygulama geliştirme süreçlerinde büyük kolaylık sağlar. INNER JOIN ile farkını tek cümlede özetlemek gerekirse: INNER JOIN kesişim kümesidir, LEFT JOIN ise sol tarafın tüm kümesidir artı kesişim.

Sysadmin olarak veritabanı sorgularıyla çalışırken en çok karşılaşacağın senaryo, ana kayıtları kaybetmeden ilgili verileri birleştirmektir. Kullanıcı tablosu, log tablosu, izin tablosu, asset tablosu gibi yapılarda LEFT JOIN seni kurtaracak. Önemli olan WHERE ve ON arasındaki farkı içselleştirmek, NULL yönetimini doğru yapmak ve her zaman EXPLAIN ile sorgunun performansını kontrol etmektir.

Bu örnekleri kendi test ortamında çalıştır, farklı varyasyonlar dene ve EXPLAIN çıktılarını incele. Veritabanı optimizasyonu teoriden çok pratik deneyimle öğrenilen bir alan, ne kadar çok elle tutup gözlemlersen o kadar hızlı gelişirsin.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir