MySQL ile Join İşlemleri

Veritabanı kurgusu iki şekilde dizayn edilir;

Birincisi allah ne verdiyse önüne, sağına, soluna bakmadan yani kurgusuz ve tembellik sonucu ortaya çıkan… İkincisi ise daha temkinli, önünü ve arkasını gören, “Ne olur ne olmaz bunu da koyalım” veya “Bunu böyle yaparsak ilerde mıçarız” önlemler dizisini kavrayan, yani “Tuvalete oturmadan önce musluğunu kontrol et bence” deyimini benimsemiş olarak dizayn edendir.

Bunları şu sebepten söylüyoruz. Günümüzde hala tablolarda boş yere veri tekrarı yapan, düzgün bir başlangıç yapmayan, bazen ise ilişkilendirme işlemlerini abartan yapılandırmalar var. Bu sebepten dolayı JOIN işlemlerinin gerekliliğini önceden iyi tespit edip veritabanı kurgusunu ona göre yapmak gerekmektedir. Bazen aşırı JOIN anahtarına tabi sorgular performans kayıplarına da yol açabilmektedir.

Aşağıdaki örnek bloklar, bu durumu çözen örneklemeler değildir fakat en azından ön ayak olur diye tahmin ediyorum.

Bir de bir kısım örneklerde A tablosundaki B kolonu şeklindeki saçmalıklar öğrenime katkının tersine daha çok okunduktan sonra sanki ot içmiş Süleyman abi hissine kaptırır. Bunun yerine direk canlı ve gerçek hayatta da kullanılan örneklemelerde bulunacağım.

Join’e dalmadan önce ilişkili tablolar nasıl olur bi bakalım. Önce egzampıl tablolarımızı oluşturalım. Özellikle tablolara fazla kolon eklemekten kaçındım yoksa kafa bulandıracaktır.

--
-- Yazilar
--
CREATE TABLE `article` (
  `id` int(11) NOT NULL auto_increment,
  `title` varchar(64) NOT NULL,
  `text` text NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

--
-- Yazilara yapilan yorumlar
--
CREATE TABLE `article_comment` (
  `id` int(11) NOT NULL auto_increment,
  `member_id` int(11) NOT NULL,
  `article_id` int(11) NOT NULL,
  `message` varchar(64) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

--
-- Yazilara yapilan yorumlari begenenler
--
CREATE TABLE `article_comment_liked` (
  `id` int(11) NOT NULL auto_increment,
  `member_id` int(11) NOT NULL,
  `article_comment_id` int(11) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

--
-- Uyeler
--
CREATE TABLE `member` (
  `id` int(11) NOT NULL auto_increment,
  `firstname` varchar(64) NOT NULL,
  `lastname` varchar(64) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Tablolar tamamdır. Yazının devamındaki örneklerde kullanmak için içlerini aşağıdaki kod bloğunu kullarak dolduralım. Bir de script içerisindeki comment çekilmiş bölümleri de okumanızı tavsiye ederim.

--
-- üye tablosuna bişeyler ekle ama sentez olsun.
-- id ler auto increment ama ben örneklerde rahat kullanabilmek için elle veriyorum. 100 serisi üyeler için gelsin
--
INSERT INTO `member` VALUES 
('101', 'Hakkı', 'Bulut'), 
('102', 'Bülent', 'Ersoy'), 
('103', 'Gevrek', 'Abi'), 
('104', 'Tiki', 'Bacı'), 
('105', 'Osmanların', 'Kiracı'), 
('106', 'Sıyrık', 'Gacı'), 
('107', 'Suzan', 'Hacı');


--
-- yazı tablosuna bişeyler ekle
-- id ler auto increment ama ben örneklerde rahat kullanabilmek için elle veriyorum. 200 serisi yazılar için gelsin.
--
INSERT INTO `article` VALUES 
('201', 'Başlık Bir', 'İçerik Bir'), 
('202', 'Başlık İki', 'İçerik İki'), 
('203', 'Başlık Üç', 'İçerik Üç'), 
('204', 'Başlık Dört', 'İçerik Dört'), 
('205', 'Başlık Beş', 'İçerik Beş'), 
('206', 'Başlık Altı', 'İçerik Altı');


--
-- yorum tablosuna bişeyler ekle
-- id ler auto increment ama ben örneklerde rahat kullanabilmek için elle veriyorum. 300 serisi begenenler için gelsin.
--
INSERT INTO `article_comment` VALUES 
('301', '101', '201', 'Olmamış.'), 
('302', '102', '201', 'Olmuş olmuş sadece makamı acem kürdi yap evladım.'), 
('303', '101', '202', 'Tebirkler güzel yazı.'), 
('304', '103', '203', 'Ayyy fecii-bokta paylaştım bunu. Çok güzeeeel.'), 
('305', '104', '203', 'Kızım düzgün yaz o nasıl Türkçe. Yazı güzel bu arada kardeşim.'), 
('306', '105', '204', 'çok iyi de oldu çok güzel iyi oldu taam mı?'), 
('307', '103', '207', 'bu yorum böyle bir blogda nasıl yayınlandı. Savcıların harekete geçmesi gerek.');


--
-- yorumları begenenler tablosuna bişeyler ekle
-- ID'ler otomatik olmasına karşın ben manuel ekleme yaptım. Sıkıntı yok devam.
--
INSERT INTO `article_comment_liked` VALUES 
('1', '101', '301'), 
('2', '101', '302'), 
('3', '102', '301'), 
('4', '103', '304'), 
('5', '103', '305'), 
('6', '103', '306'), 
('7', '104', '306'), 
('8', '105', '302'), 
('9', '106', '301'), 
('10', '106', '303'), 
('11', '107', '304');

Tablolarda doldu. O zaman asıl meseleye gelmeden son olarak JOIN’leri bilelim.

INNER JOIN

INNER JOIN, ilişkilendirildiği diğer tabloyla bire bir bütünlük sağlayarak sonuç döner. Yani INNER JOIN’inde belirtilen ON koşulundaki kolonun her iki tabloda da eşitlik sağlaması kesinliği esasına dayanır. Sadece JOIN anahtarının kullanılması INNER JOIN ile aynıdır. Kafa bulandıysa aşağıdaki örnekler netleştirecektir.

LEFT [OUTER] JOIN

LEFT JOIN, ilişkilendirildiği diğer tabloyla bire çok bütünlük sağlayarak sonuç döner. Yani LEFT JOIN’inde belirtilen tablonun solundaki tablonun koşula uysada uymasa da tüm verilerini getir anlamındadır.

RIGHT [OUTER] JOIN

RIGHT JOIN de tıpkı LEFT JOIN’deki gibi ilişkilendirildiği diğer tabloyla bire çok veri sonucu döner. LEFT JOIN’inden bir farkı yoktur desem yeridir. Sadece tabloyu sorgudaki konumlandırdığınız yer itibariyle ihtiyaç duyulur. Demek oluyorki tablonun sağındaki tüm verileri koşula uysada uymasa da getir.

İlk önce ne yapmak istediğimizi bilelim ve istediklerimizi sırayla sorgulayalım;

201 ID’li yazıya yapılan yorumlar ve yorumların sahiplerini getir. (INNER JOIN)

Bu sorguda yorum tablosunun üye ID’leriyle üye tablosunun üye ID’leri birleştiriliyor. Bunun yanında bir de yorum tablosundaki “201 ID’li olan yazıların yorumlarını getir” koşulu ile filterelemiş bulunuyoruz. Ve görüyoruz ki JOIN sayesinde üye tablosundaki yorum sahiplerinin isimlerini de yorumun hemen yanında getirebiliyoruz.

-- 201 ID'li yazıya yapılan yorumlar ve yorumların sahiplerini getir.
SELECT
  ac.message,
  m.firstname,
  m.lastname
FROM
  article_comment as ac
INNER JOIN
  member as m
ON
  ac.member_id = m.id
WHERE
  ac.article_id = 201

Sorgu Sonucu

Sadece yorum yapılmış tüm yazıları getir. (INNER JOIN)

Burada GROUP BY anahtarını kullanmadığımız zaman birleştirmeden dolayı yorum yapılmış yazılarda bir tekrar söz konusu olacaktır. Bu sebepten bunu sorguya iliştirdim. Kaldırıp sonucunu görebilirsiniz.

-- Sadece yorum yapılmış tüm yazıları getir.
SELECT
  a.*
FROM
  article as a
INNER JOIN
  article_comment as ac
ON
  a.id = ac.article_id
GROUP BY ac.article_id

Sorgu Sonucu

Yorumları like edilmiş (beğenilmiş) yazıları getir. (INNER JOIN)

Bu sorguda görüldüğü üzere iki JOIN var. Sebebi ise başlıkta da belirttiğim gibi YORUMLARI LIKE edilmiş YAZILARI getir. Yani işin içinde artık 3 tablo var. Sorgu sonucunda istediğimiz verinin tablosunu FROM’a yazdıktan sonra kalan kısmı diğer birleştirmeler…

-- Yorumları like edilmiş (beğenilmiş) yazıları getir.
SELECT
  a.*
FROM
  article as a
INNER JOIN
  article_comment as ac
ON
  ac.article_id = a.id
INNER JOIN
  article_comment_liked as acl
ON
  acl.article_comment_id = ac.id
GROUP BY ac.article_id

Sorgu Sonucu

Yorum yapılmış yazılarla birlikte yorum yapılmamış yazıları da getir. (LEFT [OUTER] JOIN)

Bu sorgu tüm yorum yapılmış yazıları getirecek fakat arada yorum yapılmamış yazıların da gelmesini istiyoruz. Bunun için LEFT JOIN kullanmak gerektiğini biliyoruz ve son olarak şunu diyoruz. Sen git bu anahtarın solundaki tabloda ne kadar veri varsa koşula uymasanda getir. Aşağıdaki sorgu sonucunda da gördüğünüz gibi tüm yazılar geldiğinden bu yazılara karşılık gelen yorumlar olmadığından NULL döndürmektedir.

-- Yorum yapılmış yazılarla birlikte yorum yapılmamış yazıları da getir.
SELECT
  a.id,
  a.title,
  ac.article_id,
  ac.message
FROM
  article as a
LEFT JOIN
  article_comment as ac
ON
  ac.article_id = a.id

Sorgu Sonucu

Tüm yazılara yapılmış yorumları yazı var olmasa da getir. (RIGHT [OUTER] JOIN)

Bu sorgu tüm yazılara yapılmış yorumları getirecek fakat yazılardan birisi küfürlü olduğundan silinmiş ve o yazıya yapılan yorum da duruyor. Normalde LEFT JOIN anahtarında soldaki tablonun tüm verilerinin gelmesi kuralı vardı. RIGHT JOIN’de ise sağ taraftaki tablonun koşula uymasa da tüm verilerini getir kuralı söz konusu. Bu sorgu sonucunda önceden küfür dolayısıyla silinmiş bir yazıya yapılan yorum da gelecektir. Fakat yazı NULL olarak dönecektir.

-- Tüm yazılara yapılmış yorumları yazı var olmasada getir.
SELECT
  a.id,
  a.title,
  ac.message
FROM
  article as a
RIGHT JOIN
  article_comment as ac
ON
  ac.article_id = a.id

Sorgu Sonucu

Bu örneklemeler bu yapı için daha da çoğaltılabilir. Sadece ne istediğimizi önceden bilelim ve kurguyu ona göre yapalım. Yoksa bir zaman sonra tüm yapıyı çöpe atmak gibi bir derdiniz olmasın.

  • Çok güzel bir yazı olmuş dostum. Niye gerçek hayattan örnek verdin ki? Biz soyut örneklerle gayet iyi anlıyorduk =)

    • Sonuna ifade vermesen yanlış anlaşılacak 🙂 Mahfetti bizi bu hayvanlar alemi ve futbolcu örnekleri kardeşim. Ahdım var kurban bayramında örnekleri verilen hayvanları tespit edip, satın alıp, kesip garibanlara dağıtıcam. Gerçi hayvanların etlerinde 1 ve 0’lar geziniyo olacak yapcak bişey yok 🙂

  • Sanem Taşçı

    Ayrıntı yazın için teşekkürler. Örnekler de gayet açıklayıcı olmuş.

  • nuknettin

    şimdi benim merak ettiğim konu şu. join ile tabloları yanyana getirmek istiyorum ama soldaki tablodaki 1 kayıta karşılık sağdaki tablodan sadece benim istediğim kriterlere uyan sadece ilk satır gelsin istiyorum bunu nasıl yaparım
    not sağdaki tabloya birden fazla koşul sağlanmakta ve birden fazla sütün gelmekte

    yardımlarınız için şimdiden teşekkürler.

  • Öncelikle ilgin için teşekkür ederiz. Burada kullanman gereken join tipini belirlemen gerekir. Şöyle ki;

    Sorgu sonucunda sağ tablodan gelen veriler sol tablodaki kolona mutlak bağlıysa ve gelmesi zorunluysa INNER olmalı. Eğer zorunluluk yoksa LEFT kullanılarak olmayan verilerinde gelmesi sağlanabilir. Sen nokta atışı ile veri bulmak istiyorsan INNER ile başlayarak aşağıdaki ON koşulundaki gibi ekstra koşul belirtebilirsin. Böylece belirlediğin bu kriterler sağ tablodan istediğin verileri getirecektir.

    Aşağıdaki örnek makaledeki ilk sorgunun güncellenmiş halidir.

    Bu sorgu yazılara yorum yazmış üyeleri getirir. Ekstra koşul ile sadece ‘101’ ID’sine sahip üyeyi getirecektir.
    ————————————-
    SELECT
    ac.message,
    m.firstname,
    m.lastname
    FROM
    article_comment as ac
    INNER JOIN
    member as m
    ON
    ac.member_id = m.id AND
    m.id = 101

    • nuknettin

      ben örnekle daha net anlatayım bir eticaret sistemi
      bir tabloda ürünler diğer tabloda ürünlere ait fiyatlar olsun
      birinci tablodan 12 adet ürün 2. tablodan ise her ürüne sadece 1 fiyat denk gelecek şekilde ve fiyat belirtilen tarihler arasında geçerli olacak inner join ile 2.tabloda birden fazla fiyat olduğundan duplicate oluyor benim istediğim 1 ürüne alt sorguda birden fazla alan ve birden fazla kriter ile sorgulama ve ayrıca alt sorguda yine order kullanımı bunun ile ilgili bir örnek verebilirmisiniz acaba ??