pillinetwork hesabınızla giriş yapın.

ikilik düzen ve veritabanı taktikleri

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, ve

1
2
1 & 0 = 0 # 1 ve 1 = 1
1 | 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 olabilir

1
2
3
4
1 - admin
2 - operatör
3 - editör
4 - kayıtlı kullanıcı

bunu normalize halde tablolara geçirirsek

1
2
3
4
5
6
7
8
9
10
11
12
13
14
users
--------
id PK
name varchar(250)
groups
--------
id PK
name varchar(250)
users_groups
--------
user_id int
group_id int

tabloya birşeyler girelim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
CREATE 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 olsun
insert into users_groups values (7,6,1);

admin grubundaki kullanıcıları çekmek istersek

1
2
3
4
5
6
7
8
9
10
11
12
13
14
select *
from
users U, users_groups UG, groups G
where
U.id = UG.user_id and UG.group_id = G.id
and 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,

1
2
3
4
5
6
7
8
9
10
11
12
13
select *
from
users U, users_groups UG
where
U.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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
select *
from
users U, users_groups UG
where
UG.user_id = U.id and
UG.group_id = 1 and
U.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,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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,

1
2
3
4
5
6
7
8
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.

1
2
3
4
5
6
7
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 = 1
2 üzeri 1 = 2
2 üzeri 2 = 4

toplamanın sonucunda 5 varsa mutlaka içinde 2 üzeri 0 vardır gibi. şimdi gruplara bakalım.

1
2
3
4
5
6
admin | operator | editor | kayitli kullanici
0 | 1 | 2 | 3
| | | |__________> 2 uzeri 3 => 8
| | |__________________> 2 uzeri 2 => 4
| |_____________________________> 2 uzeri 1 => 2
|______________________________________> 2 uzeri 0 => 1

yada ikilik duzende bunlari yazalim

0001 => 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 = 0
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0

sonuç olumsuzdur. admin ve operator (0011) u adminle (0001) karşılaştırırsak,
0 & 0 = 0
0 & 0 = 0
0 & 1 = 0
1 & 1 = 1

sonuç 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 querydede

1
update 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. boylece

1
select * 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?
define('ADMIN', 1);
define('OPERATOR', 2);
define('EDITOR', 4);
define('REGISTERED_USER', 8);
class User {
function isAdmin(){
return (ADMIN & $this->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ı

1
2
3
4
5
6
user 1 admin : E operator : H editor : H kayitli kullanici : H
user 2 admin : H operator : E editor : H kayitli kullanici : H
user 3 admin : H operator : H editor : E kayitli kullanici : H
user 4 admin : H operator : H editor : H kayitli kullanici : E
user 5 admin : H operator : H editor : H kayitli kullanici : E
user 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.

/* aybars badur yazdı. 28 Kasım 2008 10:31. 5 yorum var */

Yorumlar

çoz güzel bir yazı. Bu yöntemi bir kac open source projelerde gördüm ve kullandım bazı yerlerde ama ne yalan söylüyeyim mantığını tam kavrıyamamıştım. Saolsun aybars arkadas detayı ile anlatarak beni bu belirsizlikten kurtarmış oldu. saolsun varolsun...

Gayet detaylı bir anlatım teşekkür ederim

Form validation dışında nerede kullanılır die düşündüğüm bitwise için çok güzel bir cevap olmus.
Nasır tutmuş parmaklarına sağlık aybars badur..

Gerçekten emek harcanmış bir yazı. Teşekkürler

/* www.aliskan.web.tr Paylaştıkça çoğalıyoruz... */

users => user_id , user_name , user_group

1 , user1 , 001 // admin
2 , user2 , 010 // moderator
3 , user3 , 100 // standart

admin ve standartlar için ; 101 yani;

001 // admin
101 // aranan ( admin + standart )
-----
001 ( true, koy sepete )

010 // moderator
101 // aranan ( admin + standart )
-----
000 ( false )

100 // standart
101 // aranan ( admin + standart )
-----
100 ( true, koy sepete )

// php
$need = bindec( '101' ); // decimal olarak 5

"select ...... where ( CONV( CAST( group AS BINARY ) ,2 ,10 ) & {$need} ) = {$need} )"

yani bu şuna dönüşür;

"select ...... where ( CONV( CAST( group AS BINARY ) ,2 ,10 ) & 5 ) = 5 )"

/* -[ ::: trespass bbs | connect 2400 | usr 14400 dual standart ::: ]- */

üye olunpillinetwork sitelerine yorum ekleyebilmek ve daha fazlası için, üye olun ya da giriş yapın.

Bu yazıyı rapor et. Kural dışı içeriğe rastladığınızda editörlerimize rapor ederek müdahale edilmesini sağlayabilirsiniz. (Hangi durumlarda rapor edebilirim?)

Bu site

Nokta ve pilli ortak yapımı olan kodaman.org hep birlikte içerik üretip gelirini yazarları ile paylaştığımız kolektif bir kod yazarları blogudur. Siz de katılabilirsiniz.

pilliilan

son yorumlar

arama

pillinetwork