bir çeşit denormalizasyon konusu anlatmak istiyorum aslında, belkide programlamaya giriş derslerinde – yada kitaplarında – genellikle üstü kapalı anlatılan bitwise işlemler.muhtemelen ilk programlama kitabınızı aldığınızda, ilk sayfalarda anlatılan işlemler bit operatörleridir, 1 byte in 8 bit ettiği, ve1 & 0 = 0 # 1 ve 1 = 11 | 0 = 1 # 1 veya 1 = 1
şeklinde, hızlıca geçilen konu. aslında pratikte bit tabanlı işlemler hem çok hızlıdır hemde teorik örneklerden çok daha fazla pratik işlerde kullanılabilirler, bunun pratik kullanışıyla ilgili bir örnek:diyelim ki 4 farklı kullanıcı grubunuz var, ve bir kullanıcı birden çok gruba üye olabilir1 – admin2 – operatör3 – editör4 – kayıtlı kullanıcı
bunu normalize halde tablolara geçirirsekusers——–id PKname varchar(250)groups——–id PKname varchar(250)users_groups——–user_id intgroup_id int
tabloya birşeyler girelimCREATE TABLE `users` (`id` int(11) NOT NULL auto_increment,`name` text,PRIMARY KEY (`id`));insert into users values (1, ‘user 1’);insert into users values (2, ‘user 2’);insert into users values (3, ‘user 3’);insert into users values (4, ‘user 4’);insert into users values (5, ‘user 5’);insert into users values (6, ‘user 6’);CREATE TABLE `groups` (`id` int(11) NOT NULL auto_increment,`name` text,PRIMARY KEY (`id`));insert into groups values (1, ‘admin’);insert into groups values (2, ‘operator’);insert into groups values (3, ‘editor’);insert into groups values (4, ‘kayitli kullanici’);CREATE TABLE `users_groups` (`id` int(11) NOT NULL auto_increment,`user_id` int,`group_id` int,PRIMARY KEY (`id`));insert into users_groups values (1,1,1);insert into users_groups values (2,2,2);insert into users_groups values (3,3,3);insert into users_groups values (4,4,4);insert into users_groups values (5,5,4);insert into users_groups values (6,6,4);# 6 inci user aynı zamanda adminde olsuninsert into users_groups values (7,6,1);
admin grubundaki kullanıcıları çekmek istersekselect *fromusers U, users_groups UG, groups GwhereU.id = UG.user_id and UG.group_id = G.idand G.name = ‘admin’;+—-+——–+——–+—-+———+———-+—-+——-+| id | name | groups | id | user_id | group_id | id | name |+—-+——–+——–+—-+———+———-+—-+——-+| 1 | user 1 | 1 | 1 | 1 | 1 | 1 | admin || 6 | user 6 | 9 | 7 | 6 | 1 | 1 | admin |+—-+——–+——–+—-+———+———-+—-+——-+2 rows in set (0.00 sec)
eğer grubun id sini biliyorsak,select *fromusers U, users_groups UGwhereU.id = UG.user_id and UG.group_id = 1;+—-+——–+——–+—-+———+———-+| id | name | groups | id | user_id | group_id |+—-+——–+——–+—-+———+———-+| 1 | user 1 | 1 | 1 | 1 | 1 || 6 | user 6 | 9 | 7 | 6 | 1 |+—-+——–+——–+—-+———+———-+2 rows in set (0.00 sec)
gibi kısaltadabiliriz tabii. ama eger sadece ve sadece admine dahil olanlari cekmek istersem -tabii buna ne kadar ihtiyacimiz olur bilemiyorum – asagi yukari soyle bir query kullanmak gerekecek.select *fromusers U, users_groups UGwhereUG.user_id = U.id andUG.group_id = 1 andU.id in ( select user_id from users_groups UG group by UG.user_id having count(UG.user_id)=1);+—-+——–+——–+—-+———+———-+| id | name | groups | id | user_id | group_id |+—-+——–+——–+—-+———+———-+| 1 | user 1 | 1 | 1 | 1 | 1 |+—-+——–+——–+—-+———+———-+1 row in set (0.00 sec)
şimdi joinin maliyetinden kurtulmak istersek, yada bağlantı tablolarından kurtulmak istersek ne yapabiliriz. eğer bu şekilde sadece 4-5 farklı gruba dahil olabilecekleri bir durum varsa,alter table users add groups int default 0;update users U set groups = ( select sum(pow(2, group_id – 1)) from users_groups where user_id = U.id);mysql> select * from users;+—-+——–+——–+| id | name | groups |+—-+——–+——–+| 1 | user 1 | 1 || 2 | user 2 | 2 || 3 | user 3 | 4 || 4 | user 4 | 8 || 5 | user 5 | 8 || 6 | user 6 | 9 |+—-+——–+——–+6 rows in set (0.00 sec)
şimdi admin grubuna dahil olanları çekmek istersem,mysql> select * from users where 1 & groups;+—-+——–+——–+| id | name | groups |+—-+——–+——–+| 1 | user 1 | 1 || 6 | user 6 | 9 |+—-+——–+——–+2 rows in set (0.00 sec)
sadece admin grubuna dahil olanlari cekmek istersem.select * from users where groups = 1 ;+—-+——–+——–+| id | name | groups |+—-+——–+——–+| 1 | user 1 | 1 |+—-+——–+——–+1 row in set (0.00 sec)
sadece groups kolonu üzerinde bir index kullanarak performansı ciddi arttırabiliriz, bu şekilde tabloları join etmekten ve çetrefilli queryler kulanmaktan kurtulmuş oluyoruz, ne yaptığımın uzunca açıklaması ise şöyle, basit matematikle, şunu biliyoruz,2 üzeri 0 = 12 üzeri 1 = 22 üzeri 2 = 4toplamanın sonucunda 5 varsa mutlaka içinde 2 üzeri 0 vardır gibi. şimdi gruplara bakalım.admin | operator | editor | kayitli kullanici0 | 1 | 2 | 3| | | |__________> 2 uzeri 3 => 8| | |__________________> 2 uzeri 2 => 4| |_____________________________> 2 uzeri 1 => 2|______________________________________> 2 uzeri 0 => 1
yada ikilik duzende bunlari yazalim0001 => admin (1)0010 => operator (2)0100 => editor (4)1000 => kayitli kullanici (8)0011 => admin ve operator (3)simdi ikilik duzende 1 ve 0 in 0 ettigini ve 1 ve 1 sadece 1 ettigini hatırlayalım.bitwise 0001 (admin) ve 0010 (operator) u karşılaştırırsak,0 & 0 = 00 & 0 = 00 & 1 = 01 & 0 = 0sonuç olumsuzdur. admin ve operator (0011) u adminle (0001) karşılaştırırsak,0 & 0 = 00 & 0 = 00 & 1 = 01 & 1 = 1sonuç doğrudur. mysqlde bitwise karşılaştırmak için and yerine & operatörünü kullanıyoruz. malum olduğu üzere aslında bütün bilgiler bilgisayarda ikilik düzende durur, o yüzden bir sayıyı ikilik düzene çevirip karşılaştırmak çok ucuzdur – aslında tam sayı karşılaştırması yapmak daha pahalıdır -.şu querydedeupdate users U set groups = ( select sum(pow(2, group_id – 1)) from users_groups where user_id = U.id);
users_groups taki group_id nin 1 eksigini alip 2 uzeri sekiline cevirip sonra bunlarin toplamini aliyorum, bunu da users tablosuna denormalize ediyorum groups kolonuna. boyleceselect * from users where 1 & groups;
diyerek admin grubuna dahil olanlari çekebiliyorum bitwise karşılaştırma yaparak, bir çeşit in operatörü kullanmak gibi.aynı şekilde phpde – yada herhangi bir dilde aslında- de bir kişinin hangi gruplara üye olduğunu hiç query göndermedende hesaplayabilirsiniz.groups )? ‘E’ : ‘H’;}function isOperator(){return (OPERATOR & $this->groups) ? ‘E’ : ‘H’;}function isEditor(){return (EDITOR & $this->groups) ? ‘E’ : ‘H’;}function isRegisteredUser(){return (REGISTERED_USER & $this->groups) ? ‘E’ : ‘H’;}}mysql_connect(‘localhost’,’root’,’123′);$db = mysql_select_db(‘yazilar’);$result = mysql_query(“select * from users “);while ($user = mysql_fetch_object($result, ‘User’) ){echo $user->name . ” admin : ” . $user->isAdmin() . “t operator : ” . $user->isOperator(). “t editor : ” . $user->isEditor().”t kayitli kullanici : ” . $user->isRegisteredUser().” n”;} çıktısıuser 1 admin : E operator : H editor : H kayitli kullanici : Huser 2 admin : H operator : E editor : H kayitli kullanici : Huser 3 admin : H operator : H editor : E kayitli kullanici : Huser 4 admin : H operator : H editor : H kayitli kullanici : Euser 5 admin : H operator : H editor : H kayitli kullanici : Euser 6 admin : E operator : H editor : H kayitli kullanici : E
bu yöntemi kullanmaya karar verirseniz, klasik denormalize dezavantajlarının yanında, 2 lik düzene çevireceğiniz kümenin sayısınıda gözardı etmemeniz gerektiği, ufak subsetlerde kullanışlı olsada, 10.000 kategorilik ürün ağacınız varsa, bu şekilde denormalize etmeye çalıştığınızda – ki edemezsiniz aslında, 2 üzeri 10000 inin karşılığı inf tir mysqlde – 2 üzeri 10.000 ve toplamları ile uğraşmanız gerekecektir, ama ufak subsetlerde oldukça kullanışlıdır.birde yazının sonlarına gelmişken, akıl sağlığınıza önem veriyorsanız, mutlaka önce normalize edip sonra denormalize ediniz.