MySQL ile Hiyerarşik Veriler
Beni forumlarda ‘kategori sistemimim nasıl olmalı?’ , ‘Gruplu, kullanıcı sistemim nasıl olmalı?’, ‘Kullanıcı sayfa yetkilendirmelerini nasıl kurgulamalıyım’ gibi hiyerarşik veri üzerine sorulan sorulara verdiğim cevaplarla görenler, sık sık MySQL’in hiyerarşik dizayna dair makalesini öne sürdüğümü görmüşlerdir. Fakat makale, kendisinin ingilizce olması nedeniyle hem ingilizce gerektirdiğinden, ingilizce bilenler içinse ingilizce terminoloji eksikliğinden dolayı bir takım yeterince fayda sağlayamıyor. Üstüme çevirmenlik vazife değil ama, olduğu kadarıyla ve sabrımın yettiği kadarıyla açıklayarak türkçe kaynakda çalışma zorunluluğu olan arkadaşlara yardımcı olabilirsem; bunun mutluluğunu, huzurunu ve tabii egosunu yaşacağım. Ayrıca orada burada ukalalık ederken daha rahat olacağım gibi geliyor :-) Öncelikle bu makale, tıpkı ingilizce sürümündeki gibi, bilindik kategori sistemini örnek verecek, fakat unutmayın, bu sistemi ister kategorilerde, ister ACL ( access control list / yetki hiyerarşisi) oluştururken, ister kullanıcı hiyerarşisi oluştururken kullanabilirsiniz. Bu makalede doğal olarak makale sahibinin kim olduğu düşünülürse MySQL kullanılarak anlatıldı fakat buradaki yapı illa MySQL ile kullanılacak diye bir kaide de yok. Örnekler SQL ile verilse de tasarım ister Postgre’de ister Oracle’da isterseniz XML’de tanımlanır. Elinizde bu durumda olması gereken tek önemli şey, işlevlerinizi rahatça SQL gibi bir sorgu aracı. Makale birkaç sayfadan, bunlar Eski sayfaları kaldırdım, yazı aşağıdaki bölümlerden tek sayfa halinde oluşmakta;
- Gökçe’nin Açıklaması ve bu girişiminin nedenleri
- MySQL Makalesine Başlangıç
- Tümleşik Yapı Kalıbı (The Adjacency List Model)
- Yerleşim Yapı Kalıbı ( Nested Set Model)
Sayfaları bulmakta zorlananlar için, Kaynakça’nın hemen altında 1..2..3.. diye gidiyor.
Başlangıç
Bir çok kullanıcı, zamanı gelince mutlaka hiyerarşik bir veri yapısı gereksinimine duymuşlardır. Fakat bu yapı, ister XML ister MySQL olsun herhangi bir ilişkisel veritabanı özelliği değildir. İlişkisel veritabanları ( XML gibi) hiyerarşik veriyi tutmak için hiçbir öntanım içermez, dolayısıyla bu anlamda düz metinde satırları satır satır tutmaktan daha ileri bir teknik içermez. Hiyerarşik data, aile > çocuk ilişkisinin, ilişkisel veritabanınnda veritabanı tasarımcısı tarafından tanımlanmasıyla yönetilebilir. Burada bizim amacımız, hiyerarşik herhangi bir datayı tanımlayabilmek, ve aile > çocuk ilişkisini kurarak buna göre işlem yapabilmek. Hiyerarşik data pek çok uygulamada ve gerçek hayat örneklerinde bulunabilir, örneğin: Organizma > organlar (kalp, beyin, mide..) > dokular (…) > hücre > çekirdek, endoplasmik retikulum vb Patronlar, Ortaklar > Genel Müdür > Müdürler > Müdür Yardımcıları > Personel Ayakkabılar > Bayan Ayakkabıları, Erkek Ayakkabıları Admin > Moderator > Operator > User Bilgisayarım > Belgelerim > Resimlerim > a.jpg, b.jpg .. .. gibi kök dizinden başlayıp (evrensel küme), kendisinden küçük kümelere (alt kümeler) ayrılabilecek her veri hiyerarşik veridir. Bundan sonraki hiyerarşik örnek, bir elektronik satış mağazasına göre uyarlandı. Elimizdeki hiyerarşi şöyle: (tıklatıp, büyütebilirsiniz)
Şimdi bu veriyi, MySQL üzerinde nasıl tanımlayacağımıza, tanımladığımız veri üzerinden nasıl işlevler yapacağımızı anlatacağız. İki yöntem var, ilk bakışta basitliği göze çarpan ‘Tümleşik (Adjacency) Yapı’ ve daha karışık, ama işlevsel olan ‘Yerleşik (Nested) Yapı’.
Biz geleneksel ve en çok rastlanan ‘Tümleşik Yapı’ ile başlayacağız; sonraki sayfada görebileceğiniz gibi.
Tümleşik (Adjacency) Yapı Kalıbı
Örnekteki gibi öncelikle tablolumuzu ve verilerini oluşturuyoruz, burada yaparken takip edebileseniz diye CREATE yönergeleriyle birlikte verdim. İleride yapıyı daha iyi anlayacağınızı umuyorum.
CREATE TABLE category(
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
parent INT DEFAULT NULL);
Tümleşik yapı modelinde butün objelerin (item node) bir referans noktası (parent) vardır. Kısaca, her çocuğun bir babasının olması gibi. Fakat en yüksek dereceli objelerin, doğası gereği diğerlerinin aksine bir üstü yani parent’ı, anası yoktur. Bu yüzden, verinin kendisinde kendilerinin parent’ı NULL veya 0 kullanılır. Ben NULL kullanmayı uygun görüyorum, zira sıfır da bir ifade. Yapıyı anlamak çok zor değil, hiyerarşi örneğimizde FLASH > MP3 Player > Portable Electronics > NULL olduğunu çıkarabiliriz. Bu durumda tanımımız SQL’de INSERT halinde örnekleyelim, böylece elinizde yapının kendisi de bulunsun:
INSERT INTO category
VALUES(1,'ELECTRONICS',NULL),(2,'TELEVISIONS',1),(3,'TUBE',2),
(4,'LCD',2),(5,'PLASMA',2),(6,'PORTABLE ELECTRONICS',1),
(7,'MP3 PLAYERS',6),(8,'FLASH',7),
(9,'CD PLAYERS',6),(10,'2 WAY RADIOS',6);
SELECT * FROM category ORDER BY category_id;
+-------------+----------------------+--------+
| category_id | name | parent |
+-------------+----------------------+--------+
| 1 | ELECTRONICS | NULL |
| 2 | TELEVISIONS | 1 |
| 3 | TUBE | 2 |
| 4 | LCD | 2 |
| 5 | PLASMA | 2 |
| 6 | PORTABLE ELECTRONICS | 1 |
| 7 | MP3 PLAYERS | 6 |
| 8 | FLASH | 7 |
| 9 | CD PLAYERS | 6 |
| 10 | 2 WAY RADIOS | 6 |
+-------------+----------------------+--------+
10 rows in set (0.00 sec)
Verimizi de yerleştirdiğimize göre, artık üzerinde bir takım çokça kullanacağımız işlemleri gerçekleştirebiliriz.
Bütün hiyerarşi ağacını getirmek
Hiyerarşik verilerle uğraşırken, en yaygın işlemlerden biri bütün ağacı göstermektir. Özellikle kategoriler ve kullanıcı hiyerarşilerini gösterirken, çeşitli uygulamalarda sıkça kullanılır. Saf SQL ile bu veriyi oluşturalım:
SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS';
+-------------+----------------------+--------------+-------+
| lev1 | lev2 | lev3 | lev4 |
+-------------+----------------------+--------------+-------+
| ELECTRONICS | TELEVISIONS | TUBE | NULL |
| ELECTRONICS | TELEVISIONS | LCD | NULL |
| ELECTRONICS | TELEVISIONS | PLASMA | NULL |
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS | FLASH |
| ELECTRONICS | PORTABLE ELECTRONICS | CD PLAYERS | NULL |
| ELECTRONICS | PORTABLE ELECTRONICS | 2 WAY RADIOS | NULL |
+-------------+----------------------+--------------+-------+
6 rows in set (0.00 sec)
En alt tabakayı getirmek
Butün taban objelerini (hiyerarşinin en altında olanlar) LEFT JOIN ile getirmek için:
SELECT t1.name FROM
category AS t1 LEFT JOIN category as t2
ON t1.category_id = t2.parent
WHERE t2.category_id IS NULL;
+--------------+
| name |
+--------------+
| TUBE |
| LCD |
| PLASMA |
| FLASH |
| CD PLAYERS |
| 2 WAY RADIOS |
+--------------+
Tek bir yolu getirmek
Hiyearşide tek bir yol haritası çizmek için baş ve son referans noktaları gerekir. Nitekim, her babanın birden çok çocuğu olabilir, biz bir çocuk, ve bir büyükbaba arasında yol çizmek istiyorsak, çocuğu da , babasını da belirtmek zorundayız. Bu durumu örneğimizle gerçekleştirirsek, SQL’imiz aşağıdaki gibi olacaktır.
SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS' AND t4.name = 'FLASH';
+-------------+----------------------+-------------+-------+
| lev1 | lev2 | lev3 | lev4 |
+-------------+----------------------+-------------+-------+
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS | FLASH |
+-------------+----------------------+-------------+-------+
1 row in set (0.01 sec)
Bu yöntemin en önemli dezavantajı, bu işlevi gerçekleştirirken birden çok seviye olacağından, hangi seviyeye ait olduğunu malesef bu yapıda önceden bilmek zorundayız. Daha yalın haliyle, en büyük sorun, aşağılardan seçilen bir objenin, en üstünün kim olduğunu bilmememiz.
Tümleşik Yapı Modelinin Sınırları
Bu modelin en büyük sıkıntısı, karmaşık işlevlerde saf SQL ile işin içinden çıkılması oldukça zor olmasıdır. Bir önceki örnekte gördüğünüz gibi, bir objenin tam hiyearşi haritasını çıkarabilmek için en azından hangi seviyeye ait olduğunu bilmek zorundayız. Ayrıca, bir objeyi silerken, ek bir dikkat göstermemiz gerekir, nitekim sildiğimiz obje, diğer alt objelerin tamamının zincirini bozabilecek bir etki yaratabilir. (Elektronik objesini sildiğimizde, alt kategorileri öksüz kalırlar) Yine de bu türden istenmeyen durumların bazıları hardcode veya stored procedure’lerle giderilebilir. Genelde bu model, procedural betiklerle güçlendirilir, en alt noda’dan en üstünü getirme rekürsif fonksiyonlar (recursive funtions) veya alt nodelarını temizleyen betiklerle desteklenebilir.
Yerleşim Yapı Kalıbı
Eğer bu model size uymadıysa, daha iyi kurgulanmış bir model için bir sonraki yazımı bekleyebilirsiniz.


Ağustos 23rd, 2008 @ 5:00 am
Teşekkür ederim. Bahsettiğiniz bir sonraki yazınızı bekliyor olacağım.
Eylül 4th, 2008 @ 1:29 am
Çok yerinde bir çeviri olmuş elinize sağlık. İç-içe serileri de sabırsızlıkla bekliyoruz.
Burdan sonra da ver elini php-manual inşallah..
Eylül 4th, 2008 @ 11:09 am
İnşalllaaah.. şaka bir yana, umarım ona yeterli ekip ve zamanımız olur. Nitekim tr.php.net ‘e hevesle girip ingilizce manuali görünce hevesleri kursağında kalan birçok potansiyel PHP geliştiricisi geri adım atıyordur.
Yerleşik yapı kalıbı diye çevirmiştim, aslında iç içe daha doğru dediğin gibi. Neyse, “Nested Set” modelini de yakınlarda sunarım, şu anda karalama durumunda.
Ekim 3rd, 2008 @ 2:19 am
[...] hem bu, hem bir önceki Tümleşik Yapı Modeli işinize yaramıştır. Bu yöntem ve algoritma arasında bir karşılaştırma yapılabilir, fakat [...]