Ders 09 / 18
Çapraz ve Kendi Kendine Birleştirme
Koşulsuz birleştirmenin ürettiği kartezyen çarpım, kazara çarpımın belirtileri, bir tablonun iki takma adla kendisine bağlanması ve yinelenen çiftlerin elenmesi.
İçindekiler
Önceki iki ders birleştirmeyi bir eşitlik koşulu üzerinden kurdu: soldaki satırın yabancı anahtarı, sağdaki satırın birincil anahtarına eşit. Koşul her zaman böyle olmak zorunda değil — hatta koşulun hiç olmadığı bir birleştirme de tanımlıdır.
Bu ders iki özel duruma bakar. Birincisi koşulsuz birleştirme: her satırın her satırla eşleştiği çarpım. İkincisi bir tablonun kendisiyle birleştirilmesi: aynı tablodan gelen iki satırı karşılaştıran sorgular. İkisi de kuralın istisnası değil, aynı kuralın uç durumlarıdır.
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
Kartezyen Çarpım
Çapraz birleştirme (cross join) hiçbir koşul almaz: soldaki her satır, sağdaki her satırla eşleştirilir. Sonucun satır sayısı iki tablonun satır sayılarının çarpımıdır. Kümeler kuramındaki karşılığı kartezyen çarpımdır (Cartesian product).
sqlite3 kutuphane.db <<'SQL' .headers on .mode column SELECT COUNT(*) AS kitap_satiri FROM kitap; SELECT COUNT(*) AS sube_satiri FROM sube; SELECT COUNT(*) AS carpim FROM kitap CROSS JOIN sube; SQL
kitap_satiri ------------ 7 sube_satiri ----------- 4 carpim ------ 28
Yedi çarpı dört, yirmi sekiz. Çarpımın kendisi küçük bir örnekte zararsız görünür; on bin satırlı iki tabloda yüz milyon satır demektir. Bu, sorgunun yanlış yazılmasıyla ortaya çıkabilecek en pahalı sonuçtur ve İleri SQL kursunda sorgu planı okunurken ilk aranan belirtilerden biridir.
Sonucu görünür bir boyutta tutmak için sağ taraf süzülebilir.
sqlite3 kutuphane.db <<'SQL' .headers on .mode column SELECT s.ad AS sube, k.baslik FROM sube AS s CROSS JOIN kitap AS k WHERE k.yazar = 'Oğuz Atay' ORDER BY s.sube_id, k.kitap_id; SQL
sube baslik ------------ ----------------- Merkez Tutunamayanlar Merkez Tehlikeli Oyunlar Bahçelievler Tutunamayanlar Bahçelievler Tehlikeli Oyunlar Kadıköy Tutunamayanlar Kadıköy Tehlikeli Oyunlar Konak Tutunamayanlar Konak Tehlikeli Oyunlar
Dört şube, iki kitap, sekiz satır. Hiçbir satır “bu kitap bu şubede” demiyor — çapraz birleştirme bir olgu değil, bir olasılık listesi üretir. Kullanışlı olduğu yer de budur: her şube ile her kitabın olası her bileşimini kurup, gerçekleşenleri dış birleştirmeyle üzerine bindirmek, boş hücreleri de görünür kılan tam bir rapor ızgarası verir.
Kazara Çarpım
Çapraz birleştirme çoğunlukla kasten yazılmaz; birleştirme koşulu unutulduğunda ortaya
çıkar. Virgülle ayrılmış eski yazımda koşul WHERE yan tümcesine bırakıldığı için bu
unutma özellikle kolaydır.
sqlite3 kutuphane.db <<'SQL' .headers on .mode column SELECT COUNT(*) AS kosulsuz FROM kitap, sube; SQL
kosulsuz -------- 28
Sonuç, açıkça yazılmış çapraz birleştirmeyle aynı: yirmi sekiz. Sorgu hata vermedi. Bir önceki derste aynı iki tablonun koşullu birleştirmesi altı satır döndürüyordu.
Belirti tanınabilir: beklenenden çok daha fazla satır, tekrar eden değerler ve toplamların
katlanması. Önlem de yazımsaldır — JOIN … ON biçimi kullanıldığında koşul birleştirmenin
yanında durur ve unutulduğunda göze çarpar. CROSS JOIN yazımı da kasıtlı olduğunu
belgeler; sorguyu okuyan kişi çarpımın niyet olduğunu görür.
Kendi Kendine Birleştirme
Bir tablo kendisiyle de birleştirilebilir. Bunun için tabloya iki farklı takma ad verilir; motor açısından bu, iki ayrı tablo gibidir. Yazımın adı kendi kendine birleştirmedir (self join) ve ayrı bir birleştirme türü değildir — iç ya da dış birleştirmenin, iki tarafında aynı tablonun bulunduğu bir örneğidir.
Aynı yazarın kitap çiftlerini bulan sorgu, önce koşulsuz haliyle yazılsın.
sqlite3 kutuphane.db <<'SQL' .headers on .mode column SELECT a.baslik AS a_kitap, b.baslik AS b_kitap FROM kitap AS a JOIN kitap AS b ON a.yazar = b.yazar ORDER BY a.kitap_id, b.kitap_id; SQL
a_kitap b_kitap ----------------- ----------------- Körlük Körlük Tutunamayanlar Tutunamayanlar Tutunamayanlar Tehlikeli Oyunlar Kum Kitabı Kum Kitabı Yaban Yaban Sessiz Ev Sessiz Ev Anayurt Oteli Anayurt Oteli Tehlikeli Oyunlar Tutunamayanlar Tehlikeli Oyunlar Tehlikeli Oyunlar
Dokuz satırın yalnız ikisi anlamlı, o ikisi de aynı çiftin iki yönü. Kendi kendine birleştirmenin iki tipik kusuru burada görünüyor. Birincisi, her satır kendisiyle eşleşiyor — yazarı kendine eşit olduğu için. İkincisi, gerçek çiftler iki kez, yönleri ters çevrilmiş olarak çıkıyor.
İkisi de tek bir koşulla giderilir: eşleştirmeyi birincil anahtarın sırasına bağlamak.
sqlite3 kutuphane.db <<'SQL' .headers on .mode column SELECT a.yazar, a.baslik AS ilk, b.baslik AS ikinci FROM kitap AS a JOIN kitap AS b ON a.yazar = b.yazar AND a.kitap_id < b.kitap_id ORDER BY a.kitap_id; SQL
yazar ilk ikinci --------- -------------- ----------------- Oğuz Atay Tutunamayanlar Tehlikeli Oyunlar
a.kitap_id < b.kitap_id koşulu iki işi birden yapıyor. Eşitlik dışlandığı için satır
kendisiyle eşleşemiyor; sıralama tek yönlü olduğu için her çift bir kez çıkıyor. Kesin
eşitsizlik yerine <> yazılsaydı yalnız birinci sorun çözülür, aynalı yinelemeler kalırdı.
Aynı kalıp başka bir soruya uygulanabilir: aynı şehirdeki şube çiftleri.
sqlite3 kutuphane.db <<'SQL' .headers on .mode column SELECT a.sehir, a.ad AS sube_1, b.ad AS sube_2 FROM sube AS a JOIN sube AS b ON a.sehir = b.sehir AND a.sube_id < b.sube_id; SQL
sehir sube_1 sube_2 ------ ------ ------------ Ankara Merkez Bahçelievler
Kendi Kendine Birleştirmeyi Zincirlemek
Kendi kendine birleştirme başka birleştirmelerle birlikte kullanılabilir. Aynı kitabı ödünç almış üye çiftlerini bulmak için ödünç tablosu kendisiyle, sonuç ise üye ve kitap tablolarıyla birleştirilir.
sqlite3 kutuphane.db <<'SQL' .headers on .mode column SELECT k.baslik, u1.ad AS uye_1, u2.ad AS uye_2 FROM odunc AS o1 JOIN odunc AS o2 ON o1.kitap_id = o2.kitap_id AND o1.uye_id < o2.uye_id JOIN uye AS u1 ON u1.uye_id = o1.uye_id JOIN uye AS u2 ON u2.uye_id = o2.uye_id JOIN kitap AS k ON k.kitap_id = o1.kitap_id ORDER BY k.kitap_id, u1.uye_id, u2.uye_id; SQL
baslik uye_1 uye_2 -------------- ------ ------ Körlük Ayşe Mehmet Körlük Ayşe Emre Körlük Mehmet Emre Tutunamayanlar Ayşe Selin Kum Kitabı Zeynep Selin Yaban Zeynep Emre
Beş tablo başvurusu var ama tablo sayısı üç: ödünç tablosu iki kez, üye tablosu iki kez
geçiyor. Takma adlar burada isteğe bağlı değil, zorunludur — hangi uye_id sütunundan söz
edildiğini başka hiçbir şey belirleyemez.
Körlük üç satır üretti çünkü üç ayrı üye almış: üç öğeden seçilen ikili sayısı üçtür.
Kendi kendine birleştirmenin satır sayısı, gruptaki eleman sayısının karesiyle büyür;
büyük gruplarda bu, çapraz birleştirme kadar pahalı olabilir.
Özet
- Çapraz birleştirme koşulsuzdur; satır sayısı iki tablonun satır sayılarının çarpımıdır.
- Çarpım bir olgu listesi değil olasılık listesi üretir; rapor ızgarası kurmakta işe yarar.
- Birleştirme koşulu unutulduğunda kazara çarpım hata vermeden oluşur; belirtisi beklenenden çok satır ve katlanan toplamlardır.
- Kendi kendine birleştirme ayrı bir tür değildir; aynı tabloya iki takma adla başvurmaktır ve takma ad zorunludur.
- Koşulsuz kendi kendine birleştirme her satırı kendisiyle ve her çifti iki yönde eşleştirir; birincil anahtar üzerinde kesin eşitsizlik ikisini birden giderir.
- Çift üreten birleştirmelerin maliyeti grup büyüklüğünün karesiyle artar.
Sonraki Adım
Buraya kadarki bütün sorgular satır düzeyinde kaldı: her sonuç satırı, kaynak satırların bir bileşimine karşılık geliyordu. Oysa “kaç kitap”, “ortalama ödünç süresi”, “en eski basım” gibi sorular satırları değil, satır kümelerini özetler. Sonraki ders satır kümesini tek değere indirgeyen toplama işlevlerini kurar ve bu işlevlerin boş değerleri neden saymadığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.