İçeriğe geç
academia.sh

Ders 07 / 18

İç Birleştirme

Normalleştirilmiş şemada dağılmış bilginin tek sonuçta toplanması, birleştirme koşulu, tablo takma adları, eşleşmeyen satırların düşmesi ve satır sayısının çoğalması.

İçindekiler

Önceki konu tek tablodan okuyan sorguları kurdu: sütun seçimi, koşul, sıralama, boş değer davranışı, işlevler. Bu araçlarla yanıtlanamayan bir soru kaldı. Ödünç tablosuna bakıldığında hangi üyenin hangi kitabı aldığı görünmez.

Aşağıdaki blok şemayı ve örnek veriyi kurar; dersin bütün sorguları bu blokta oluşturulan kutuphane.db dosyası üzerinde çalışır.

rm -f kutuphane.db
sqlite3 kutuphane.db <<'SQL'
CREATE TABLE sube (sube_id INTEGER PRIMARY KEY, ad TEXT NOT NULL, sehir TEXT NOT NULL);
CREATE TABLE kitap (kitap_id INTEGER PRIMARY KEY, baslik TEXT NOT NULL, yazar TEXT NOT NULL,
                    basim_yili INTEGER, sube_id INTEGER REFERENCES sube(sube_id));
CREATE TABLE uye (uye_id INTEGER PRIMARY KEY, ad TEXT NOT NULL, soyad TEXT NOT NULL,
                  eposta TEXT, kayit_tarihi TEXT NOT NULL);
CREATE TABLE odunc (odunc_id INTEGER PRIMARY KEY, kitap_id INTEGER NOT NULL REFERENCES kitap(kitap_id),
                    uye_id INTEGER NOT NULL REFERENCES uye(uye_id), alis_tarihi TEXT NOT NULL,
                    iade_tarihi TEXT);
INSERT INTO sube VALUES (1,'Merkez','Ankara'),(2,'Bahçelievler','Ankara'),
  (3,'Kadıköy','İstanbul'),(4,'Konak','İzmir');
INSERT INTO kitap VALUES (1,'Körlük','José Saramago',1995,1),(2,'Tutunamayanlar','Oğuz Atay',1972,1),
  (3,'Kum Kitabı','Jorge Luis Borges',1975,2),(4,'Yaban','Yakup Kadri',1932,2),
  (5,'Sessiz Ev','Orhan Pamuk',1983,3),(6,'Anayurt Oteli','Yusuf Atılgan',NULL,3),
  (7,'Tehlikeli Oyunlar','Oğuz Atay',1973,NULL);
INSERT INTO uye VALUES (1,'Ayşe','Demir','[email protected]','2023-02-14'),
  (2,'Mehmet','Kaya','[email protected]','2023-05-30'),(3,'Zeynep','Arslan',NULL,'2024-01-09'),
  (4,'Emre','Yıldız','[email protected]','2024-03-22'),(5,'Selin','Aydın',NULL,'2024-11-05'),
  (6,'Burak','Şahin','[email protected]','2025-01-18');
INSERT INTO odunc VALUES (1,1,1,'2025-01-10','2025-01-24'),(2,2,1,'2025-02-02','2025-02-20'),
  (3,1,2,'2025-02-11',NULL),(4,3,3,'2025-03-01','2025-03-15'),(5,4,3,'2025-03-18','2025-04-02'),
  (6,1,4,'2025-04-05','2025-04-19'),(7,5,4,'2025-04-21',NULL),(8,2,5,'2025-05-02','2025-05-30'),
  (9,7,1,'2025-05-14','2025-05-28'),(10,3,5,'2025-06-03',NULL),(11,6,2,'2025-06-11','2025-06-25'),
  (12,4,4,'2025-06-20','2025-07-04');
SQL
sqlite3 kutuphane.db <<'SQL'
.headers on
.mode column
SELECT odunc_id, kitap_id, uye_id, alis_tarihi FROM odunc WHERE odunc_id <= 4 ORDER BY odunc_id;
SQL
odunc_id  kitap_id  uye_id  alis_tarihi
--------  --------  ------  -----------
1         1         1       2025-01-10 
2         2         1       2025-02-02 
3         1         2       2025-02-11 
4         3         3       2025-03-01 

Görünen tek şey kimlikler. Bu bir eksiklik değil, normalleştirmenin doğrudan sonucudur: üyenin adı üye tablosunda bir kez yazılır, ödünç kaydında yalnız ona başvuran yabancı anahtar bulunur. Bilgi bilerek dağıtılmıştır; sorgunun işi onu geçici olarak toplamaktır. Toplama işlemine birleştirme (join) denir.

Birleştirme Koşulu

Birleştirme, iki tablonun satırlarını bir koşula göre eşleştirir. Yazımı JOIN ve ON yan tümceleriyle kurulur.

sqlite3 kutuphane.db <<'SQL'
.headers on
.mode column
SELECT o.odunc_id, u.ad, u.soyad, o.alis_tarihi
FROM odunc AS o
JOIN uye AS u ON o.uye_id = u.uye_id
WHERE o.odunc_id <= 4
ORDER BY o.odunc_id;
SQL
odunc_id  ad      soyad   alis_tarihi
--------  ------  ------  -----------
1         Ayşe    Demir   2025-01-10 
2         Ayşe    Demir   2025-02-02 
3         Mehmet  Kaya    2025-02-11 
4         Zeynep  Arslan  2025-03-01 

Kimlikler adlara dönüştü. ON yan tümcesindeki koşul, ödünç kaydındaki yabancı anahtarı üye tablosundaki birincil anahtarla eşliyor. Bu, birleştirmenin en yaygın biçimidir: eşitlik koşuluyla yazılan ve iç birleştirme (inner join) adını taşıyan biçim. JOIN sözcüğü tek başına yazıldığında iç birleştirme anlaşılır; INNER JOIN yazımı da aynı şeydir.

AS o ve AS u yazımları tablolara takma ad verir. İki tabloda aynı adlı sütunlar bulunduğunda — burada iki tabloda da uye_id var — hangi tablonun sütunundan söz edildiğini takma ad belirler. Tek harflik takma adlar yaygındır ama tablo sayısı arttıkça okunabilirliği düşürür; anlamlı kısaltmalar daha iyidir.

Birden Çok Tablo

Birleştirme zincirlenebilir. Ödünç kaydı hem üyeye hem kitaba başvurduğu için, iki birleştirme sorunun tamamını yanıtlar.

sqlite3 kutuphane.db <<'SQL'
.headers on
.mode column
SELECT o.odunc_id, u.ad || ' ' || u.soyad AS uye, k.baslik, o.alis_tarihi
FROM odunc AS o
JOIN uye AS u   ON o.uye_id = u.uye_id
JOIN kitap AS k ON o.kitap_id = k.kitap_id
ORDER BY o.odunc_id;
SQL
odunc_id  uye            baslik             alis_tarihi
--------  -------------  -----------------  -----------
1         Ayşe Demir     Körlük             2025-01-10 
2         Ayşe Demir     Tutunamayanlar     2025-02-02 
3         Mehmet Kaya    Körlük             2025-02-11 
4         Zeynep Arslan  Kum Kitabı         2025-03-01 
5         Zeynep Arslan  Yaban              2025-03-18 
6         Emre Yıldız    Körlük             2025-04-05 
7         Emre Yıldız    Sessiz Ev          2025-04-21 
8         Selin Aydın    Tutunamayanlar     2025-05-02 
9         Ayşe Demir     Tehlikeli Oyunlar  2025-05-14 
10        Selin Aydın    Kum Kitabı         2025-06-03 
11        Mehmet Kaya    Anayurt Oteli      2025-06-11 
12        Emre Yıldız    Yaban              2025-06-20 

Ödünç tablosundaki on iki kaydın hepsi döndü. Her birleştirme sonucu yeni bir bağıntıdır ve bir sonraki birleştirmenin girdisi olur; sıra, yazıldığı sıradır ama motorun uyguladığı sıra farklı olabilir — bildirimsel dilin kuralı burada da geçerlidir.

Eşleşmeyen Satırlar Düşer

İç birleştirmenin tanımlayıcı özelliği, koşulu sağlamayan satırların sonuçta hiç görünmemesidir. Kitapları şubeleriyle birleştiren sorgu bunu gösteriyor.

sqlite3 kutuphane.db <<'SQL'
.headers on
.mode column
SELECT k.baslik, s.ad AS sube
FROM kitap AS k
JOIN sube AS s ON k.sube_id = s.sube_id
ORDER BY k.kitap_id;
SQL
baslik          sube        
--------------  ------------
Körlük          Merkez      
Tutunamayanlar  Merkez      
Kum Kitabı      Bahçelievler
Yaban           Bahçelievler
Sessiz Ev       Kadıköy     
Anayurt Oteli   Kadıköy     

Altı satır döndü, oysa kitap tablosunda yedi kitap var. Eksik olan Tehlikeli Oyunlar: şube kimliği boş olduğu için k.sube_id = s.sube_id koşulu hiçbir şube satırı için doğru olmuyor. Beşinci dersteki kural burada da işliyor — boş değerle yapılan eşitlik ne doğru ne yanlış üretir, ve birleştirme de yalnız doğruyu geçirir.

sqlite3 kutuphane.db <<'SQL'
.headers on
.mode column
SELECT COUNT(*) AS kitap FROM kitap;
SELECT COUNT(*) AS birlestirme_sonucu FROM kitap AS k JOIN sube AS s ON k.sube_id = s.sube_id;
SQL
kitap
-----
7    
birlestirme_sonucu
------------------
6                 

Kaybın sessizliği önemlidir: sorgu hata vermez, bir eksik döndürür. Rapor sayıları tutmadığında ilk bakılacak yerlerden biri, iç birleştirmenin elediği satırlardır. Eşleşmeyen satırların da korunması gerekiyorsa dış birleştirme yazılır; sonraki dersin konusu budur.

Satır Sayısı Artabilir de

Birleştirme yalnız satır düşürmez, çoğaltabilir de. Bir tablodaki bir satır, diğer tabloda birden çok satırla eşleşiyorsa sonuçta o kadar kez görünür.

sqlite3 kutuphane.db <<'SQL'
.headers on
.mode column
SELECT k.baslik, o.odunc_id, o.alis_tarihi
FROM kitap AS k
JOIN odunc AS o ON k.kitap_id = o.kitap_id
WHERE k.baslik = 'Körlük'
ORDER BY o.odunc_id;
SQL
baslik  odunc_id  alis_tarihi
------  --------  -----------
Körlük  1         2025-01-10 
Körlük  3         2025-02-11 
Körlük  6         2025-04-05 

Kitap tablosunda tek olan satır, ödünç kayıtlarıyla birleştiğinde üç kez göründü. Bu doğrudur — üç ayrı ödünç işlemi var — ama sayım yapılırken tuzağa dönüşür: bu sonuç üzerinden kitap sayılırsa Körlük üç kez sayılır. Toplama işlevleri konusunda bu ayrım tekrar ele alınacak.

Genel kural: bire-çok bir ilişkide birleştirme, “bir” tarafındaki satırları “çok” tarafındaki eşleşme sayısı kadar tekrarlar.

Kısaltılmış Yazımlar

Birleştirmenin daha kısa iki yazımı vardır ve ikisi de dikkat ister.

Birincisi, birleştirme koşulunu iki tabloda aynı adı taşıyan sütuna dayandıran USING yan tümcesidir.

sqlite3 kutuphane.db <<'SQL'
.headers on
.mode column
SELECT baslik, ad AS sube FROM kitap JOIN sube USING (sube_id) ORDER BY kitap_id;
SQL
baslik          sube        
--------------  ------------
Körlük          Merkez      
Tutunamayanlar  Merkez      
Kum Kitabı      Bahçelievler
Yaban           Bahçelievler
Sessiz Ev       Kadıköy     
Anayurt Oteli   Kadıköy     

USING (sube_id) yazımı ON k.sube_id = s.sube_id ile aynı sonucu verir ve ortak sütunu sonuçta bir kez gösterir. Adlandırma tutarlıysa yararlıdır; sütun adları farklıysa kullanılamaz.

İkincisi, birleştirme koşulunu hiç yazmayan doğal birleştirmedir (natural join): iki tabloda aynı adı taşıyan bütün sütunlar birleştirme koşulu sayılır. Kısalığı, sessiz yanlışlık pahasına gelir.

sqlite3 kutuphane.db <<'SQL'
.headers on
.mode column
SELECT 'uye NATURAL JOIN sube satır sayısı: ' || COUNT(*) AS olcum FROM uye NATURAL JOIN sube;
SQL
olcum                                
-------------------------------------
uye NATURAL JOIN sube satır sayısı: 0

Üye ile şube tablosu arasında hiçbir anlamlı ilişki yok, ama ikisinde de ad adlı bir sütun var. Doğal birleştirme bunu bir eşleştirme ölçütü sanıp üye adı ile şube adını karşılaştırdı ve sıfır satır döndürdü. Hata iletisi yok; yalnız boş bir sonuç.

Asıl tehlike, şemaya sonradan eklenen bir sütunun adının bir başka tabloyla çakışmasıdır: o gün doğru çalışan doğal birleştirme, ertesi gün sessizce başka bir şey hesaplamaya başlar. Birleştirme koşulunu açıkça yazmak, bu sınıf hatayı tümüyle ortadan kaldırır.

Eski bir yazım daha vardır: tabloları virgülle ayırıp koşulu WHERE içine koymak.

sqlite3 kutuphane.db <<'SQL'
.headers on
.mode column
SELECT k.baslik, s.ad AS sube FROM kitap AS k, sube AS s
WHERE k.sube_id = s.sube_id ORDER BY k.kitap_id;
SQL
baslik          sube        
--------------  ------------
Körlük          Merkez      
Tutunamayanlar  Merkez      
Kum Kitabı      Bahçelievler
Yaban           Bahçelievler
Sessiz Ev       Kadıköy     
Anayurt Oteli   Kadıköy     

Sonuç aynı, ama iki kusuru var. Birleştirme koşulu ile süzme koşulu aynı yan tümcede karışır; ve koşul unutulduğunda sorgu hata vermeden bütün eşleşme çiftlerini üretir. ON yazımı koşulu birleştirmenin yanında tutarak her ikisini de önler.

Özet

  • Normalleştirilmiş şemada bilgi bilerek dağıtılır; birleştirme onu sorgu süresince geçici olarak toplar.
  • İç birleştirme yalnız koşulu doğru olan satır çiftlerini döndürür; eşleşmeyen satır hata vermeden düşer.
  • Boş değer taşıyan yabancı anahtar hiçbir satırla eşleşmez ve iç birleştirmede kaybolur.
  • Bire-çok ilişkide birleştirme, “bir” tarafındaki satırı eşleşme sayısı kadar tekrarlar.
  • Tablo takma adları aynı adlı sütunları ayırt eder ve çok tablolu sorguları okunabilir kılar.
  • USING ortak adlı sütunda kısalık sağlar; doğal birleştirme koşulu ad benzerliğine bıraktığı için sessiz yanlışlığa açıktır.

Sonraki Adım

İç birleştirmenin eşleşmeyen satırı düşürmesi, kimi sorularda tam olarak istenmeyen şeydir: “hiç kitabı olmayan şube” ya da “hiç ödünç almamış üye” gibi sorular, tam da eşleşmesi olmayan satırları arar. Sonraki ders bu satırları koruyan dış birleştirmeleri ele alır, korunan satırların boş değerle nasıl dolduğunu gösterir ve sağ ile tam dış birleştirmenin motor desteğine bağlı olduğunu ölçer.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat