Dlaczego baza Wikipedii ssie…

No Comments

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:
kategorie_wiki

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_sortkeytytuł 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…

Categories: PHP, Software, SQL, Zabawy z kodem Tags: Tagi:, ,

Leave a Reply

Your email address will not be published. Required fields are marked *