Modül 1 · Container & Temel Altyapı · ⏱ 6 dk

JOIN: iki defteri yapıştırmak

JOIN gluing two notebooks

Bu derste neler öğreneceksin

  • INNER JOIN farkını açıklar
  • LEFT JOIN ne zaman kullanılır
  • Müşteri + sipariş birleştirir

JOIN: İki Tabloyu Birleştirmek

İlişkisel veritabanlarının “ilişkisel” diye anılmasının sebebi tablolar arası ilişkiler. Bir tabloda müşteriler, başka tabloda siparişler. Bu iki tabloyu birleştirip anlamlı sorgular yazabilirsen gerçek bir veri mühendisi gibi çalışmaya başlarsın.

Bugün JOIN’in temel kavramını, üç ana türünü ve gerçek bir analitik senaryoyu göreceğiz.

Veriyi Hazırla

İki tablo kur. İlki müşteri kataloğu, ikincisi siparişler:

CREATE TABLE musteriler2 (
  id SERIAL PRIMARY KEY,
  isim TEXT NOT NULL,
  telefon TEXT,
  sehir TEXT
);

CREATE TABLE siparisler (
  id SERIAL PRIMARY KEY,
  musteri_id INT NOT NULL REFERENCES musteriler2(id),
  icecek TEXT NOT NULL,
  fiyat NUMERIC(5,2) NOT NULL CHECK (fiyat >= 0),
  tarih DATE NOT NULL DEFAULT CURRENT_DATE
);

INSERT INTO musteriler2 (isim, telefon, sehir) VALUES
  ('Ayşe',  '0532-111-1111', 'İstanbul'),
  ('Mehmet','0533-222-2222', 'Ankara'),
  ('Zeynep','0534-333-3333', 'İzmir'),
  ('Ali',   '0535-444-4444', 'Bursa');

INSERT INTO siparisler (musteri_id, icecek, fiyat) VALUES
  (1, 'Limonata', 25.00),
  (1, 'Nane Limonata', 30.00),
  (3, 'Çilekli Limonata', 35.00),
  (2, 'Klasik Limonata', 25.00),
  (1, 'Buzlu Limonata', 28.00);

Burada dikkat etmen gereken şey musteri_id INT NOT NULL REFERENCES musteriler2(id). Foreign key constraint. Bu, siparisler.musteri_id değerinin mutlaka musteriler2.id içinde var olması gerektiğini söyler. Olmayan müşteri ID’si ile sipariş ekleyemezsin. Bu, referential integrity — veri kalitesinin omurgası.

JOIN Anatomisi

SELECT
  siparisler.id        AS siparis_id,
  musteriler2.isim     AS musteri_adi,
  musteriler2.sehir    AS sehir,
  siparisler.icecek,
  siparisler.fiyat,
  siparisler.tarih
FROM siparisler
INNER JOIN musteriler2 ON siparisler.musteri_id = musteriler2.id;

Üç parça:

  1. FROM siparisler — ana tablo. Soldaki.
  2. INNER JOIN musteriler2 — sağdaki tablo.
  3. ON siparisler.musteri_id = musteriler2.id — eşleşme kuralı.

Bu olmadan iki tabloyu nasıl birleştireceğini veritabanı bilemez. ON cümlesi olmadan SQL hata verir.

JOIN Türleri

INNER JOIN — Kesişim

Her iki tabloda da eşleşen satırları getir. Ali (id=4) hiç sipariş vermedi, o yüzden INNER JOIN sonucunda görünmez.

LEFT JOIN — Soldaki Hep Gelir

SELECT
  musteriler2.isim,
  COUNT(siparisler.id) AS siparis_sayisi,
  COALESCE(SUM(siparisler.fiyat), 0) AS toplam_harcama
FROM musteriler2
LEFT JOIN siparisler ON musteriler2.id = siparisler.musteri_id
GROUP BY musteriler2.isim
ORDER BY toplam_harcama DESC;

COUNT(siparisler.id) (yıldız yerine id kullandık) NULL’ları saymaz, Ali 0 görünür. SUM NULL döndürür, onu COALESCE ile 0’a çevirdik.

RIGHT JOIN — Sağdaki Hep Gelir

Pratikte LEFT JOIN ile aynı sonucu, tabloları yer değiştirerek elde edersin. Çok nadir kullanılır, karşılaşırsan şaşırma.

FULL OUTER JOIN — Birlşim

Her iki tablodaki tüm satırları getir, eşleşmeyenler NULL olur. Analitik sorgularda “şu müşteri var ama siparişi yok” + “şu sipariş var ama müşterisi silinmiş” gibi uç durumları yakalamak için kullanılır.

Çoklu JOIN

Üçüncü bir tablo ekleyelim: ürün kataloğu.

CREATE TABLE urunler (
  id SERIAL PRIMARY KEY,
  icecek TEXT UNIQUE NOT NULL,
  maliyet NUMERIC(5,2) NOT NULL
);

INSERT INTO urunler (icecek, maliyet) VALUES
  ('Limonata', 10.00),
  ('Nane Limonata', 12.00),
  ('Çilekli Limonata', 15.00),
  ('Klasik Limonata', 10.00),
  ('Buzlu Limonata', 11.00);

Şimdi kar marjını hesaplayan sorgu:

SELECT
  m.sehir,
  s.icecek,
  s.fiyat                AS satis_fiyati,
  u.maliyet              AS maliyet,
  s.fiyat - u.maliyet    AS kar,
  COUNT(*) OVER (PARTITION BY m.sehir) AS sehir_siparis_sayisi
FROM siparisler s
INNER JOIN musteriler2 m ON s.musteri_id = m.id
INNER JOIN urunler u ON s.icecek = u.icecek
ORDER BY kar DESC;

Burada tablo alias’ları (s, m, u) kullandık — üç tablolu sorguda tam isim yazmak işkence. PARTITION BY içeren COUNT(...) OVER (...) ise window function — sonraki derslerde göreceğiz. Şimdilik bil: “her satır için, o şehirden kaç sipariş geldi” bilgisini getirir.

Self JOIN

Aynı tabloyu kendisiyle birleştirmek bazen gerekir. Mesela “hangi müşteriler aynı şehirden?”:

SELECT
  a.isim AS musteri_a,
  b.isim AS musteri_b,
  a.sehir
FROM musteriler2 a
INNER JOIN musteriler2 b
  ON a.sehir = b.sehir AND a.id < b.id;

a.id < b.id kendinle eşleşmeyi engeller (A-A gelmez), aynı şehirdeki çiftleri bulur.

Performans İpucu

JOIN yavaş çalışırsa, foreign key sütununda index olup olmadığını kontrol et:

CREATE INDEX idx_siparisler_musteri_id ON siparisler(musteri_id);

Postgres çoğu durumda otomatik index oluşturmaz. Milyon satırlı tablolarda bu eksik, sorgunun 30 saniye sürmesi demek.

Alıştırma

  1. LEFT JOIN ile Ali’yi de gör, sipariş sayısı 0 çıksın.
  2. Her şehirden toplam ciroyu hesapla (SUM(fiyat) + GROUP BY sehir).
  3. Foreign key’i drop et (ALTER TABLE siparisler DROP CONSTRAINT siparisler_musteri_id_fkey;), olmayan musteri_id ile sipariş eklemeyi dene. Foreign key neden önemli, gözünle gör.

Sıradaki

Tabloları birleştirmeyi öğrendin. Şimdi altyapıyı kodla yazmaya geçiyoruz →