Dlaczego baza Wikipedii ssie…
maj 20th, 2009. Brak komentarzy.
Kategorie: PHP, SQL, Software, Zabawy z kodem.
Jestem ostatnio dziwnie nastawiony na narzekanie, prawda? No cóż, mam swoje powody :)
Zaczęło się trywialnie. “Hej, potrzebujemy plugin do firmowej Wikipedii, ktory…”. No właśnie.
Plugin ma ułatwiać przypisanie artykułu do kategorii. Albo odwrotnie.
Łatwe, prawda? Wystarczy wylistować wszystkie kategorie:
SELECT page_title FROM page WHERE page_namespace=14
page_namespace ma być równe stałej NS_CATEGORY. I tyle?
“No dobra, ale my byśmy chcieli to w postaci drzewka…”
Podpowiem, jeśli ktoś nie rozumie. Ma to wyglądać (obecnie już wygląda) mniej-więcej tak:

No i świetnie, to jazda!
Po napisaniu odpowiedniego pluginu (nota bene powyższy screen pochodzi już z wersji działającej, finalnej), wszystko było dobrze, dopóki ktoś nie wprowadził artykułu, który miał ten sam tytuł, co kategoria, z której się znajdował. Dlaczego? Zobaczmy…
Strony w Wiki są zapisane w tabeli page. Jest jakieś unikatowe ID (pole page_id), określenie rodzaju strony (page_namespace) tytuł (page_title) itp.
Zaś powiązania kategoria-artykuły leżą w tabeli categorylinks. I tu niespodzianka. Chciałoby się widzieć klucze IDkategorii / IDstrony? Jasne…
Najpierw przyjrzyjmy się strukturze tabeli:
mysql> SHOW FIELDS FROM categorylinks; +--------------+------------------+------+-----+-------------------+-----------------------------+ | Field | Type | Null | Key | Default | Extra | +--------------+------------------+------+-----+-------------------+-----------------------------+ | cl_from | int(10) unsigned | NO | PRI | 0 | | | cl_to | varchar(255) | NO | PRI | | | | cl_sortkey | varchar(70) | NO | | | | | cl_timestamp | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP | +--------------+------------------+------+-----+-------------------+-----------------------------+ 4 rows in set (0.00 sec)
Jak wygląda przypisanie strony do kategorii? W kolumnie cl_to leży tytuł kategorii, w kolumnie cl_sortkey – tytuł strony do niej przypisanej.
Czym to owocuje? Kiedy chcesz zebrać drzewko kategorii, rekurencyjnie, musisz, zaczynając od kategorii nadrzędnej, dla każdej kategorii:
1. Pobrać nazwę kategorii.
2. Pobrać wszystkie nazwy stron należących do kategorii o tej nazwie (tab. categorylinks).
3. Dla każdej z tych stron musisz pobrać page_namespace.
4. Dla wszystkich stron posiadających page_namespace == NS_CATEGORY musisz powtórzyć operację, czyli pobrać tytuły wszystkich stron należących do tych kategorii…
OK, niby logiczne. Problem pojawia się we wspomnianym przypadki. Mamy kategorię, w tej kategorii stronę o tej samej nazwie. Jak to wygląda w bazie?
mysql> SELECT page_title, page_namespace FROM page; +------------+----------------+ | page_title | page_namespace | +------------+----------------+ | MyWord | 0 | | MyWord | 14 | +------------+----------------+ 2 rows in set (0.00 sec)
No to działamy:
1. Mamy nazwę kategorii: MyWord.
2. Pobieramy wszystkie strony należące do tej kategorii:
SELECT * FROM categorylinks WHERE cl_to='MyWord';
3. Sprawdzamy wszystkie, czyli dla każdej pobranej strony:
SELECT page_namespace FROM page WHERE page_title=%KOLEJNA_STRONA%;
I co? Dla pierwszej strony o tytule MyWord namespace wynosi 0 (zwykła strona), dla drugiej – 14 (kategoria). Czyli mamy kategorię w kategorii. No to to zagnieżdżamy i… wywala sie serwer WWW :)
Dla SQLowych ortodoksów: Wiem, ze to się powinno załatwić jednym zapytaniem z JOINem; wiem, że można sprawdzić, czy NOT cl_to=cl_sortkey etc. Co, jeśli taka strona nie znajduje się w tej kategorii, tylko w innej? Ma tę samą nazwę i przy pobieraniu z categorylinks wykażemy, że jest kategorią (bo ta druga – o tej samej nazwie – jest kategorią).
Ja podałem tylko najprostszy przykład i SQLowo rozbiłem go na czynniki pierwsze, dla czytelności :)
Co należy zrobić?
Zabijcie, ale nie przyszło mi do głowy nic innego, niż zbieranie już pobranych tytułów kategorii i przechowywanie ich w tablicy globalnej, po czym sprawdzanie przez in_array(), czy już pobrałem tę kategorię. Powód: w Wiki może istnieć tylko jedna kategoria o danym tytule, niezależnie od miejsca, jakie zajmuje w drzewie kategorii. Bo przypisywanie zarówno stron, jak i kategorii podrzędnych do kategorii nadrzędnych jest dowolne.
Może się przywalam, ale gdyby powiązania kategoria-artykuł korzystały z unikatowych kluczy, a nie z nieunikatowych tytułów, byłoby dużo łatwiej…