ďťż

[PHP] Sortowanie kategorii wg hierarchii na liście

       

Podstrony


telcocafe

Wyciągam z bazy danych wszystkie kategorie. W wielowymiarowej tablicy cats ważne są pola:ID | sc (kategoria wyższa, 0 = podstawowa)

Potrzebuję teraz wyświetlić je w ten sposób, aby subkategorie znalazły się pod wyższą dla nich kategorią. Jednym ze sposobów jest przypisywanie indeksów subkategorii do ich nadkategorii, używając tablic:
$tablica[indeks_kategorii] [] = indeks_subkategorii;
To nadal nie rozwiązuje problemu. Podczas wyświetlania danych (nawet po zapisaniu indeksów głównych kategorii do tablicy) trzeba wciąż użyć pętli WHILE i powtórzyć kod HTML (w głównej pętli FOR oraz we WHILE).

Można jeszcze przed wyświetleniem danych posortować indeksy kategorii do 1 tablicy na podstawie wcześniej utworzonych tablic.

Zależy mi na tym, aby przygotować dane przy użyciu jak najmniejszej ilości pętli oraz wyświetlić listę kategorii używając 1 pętli (bez zagnieżdżania innych w środku). :) Może znacie lepsze i wydajniejsze rozwiązania? ;)

PS. Nie chcę zmieniać struktury tabeli (ewentualnie w ostateczności mogę dodać dodatkowe pole).

PS2. Czy możliwe jest posortowanie kategorii w ten sposób przez bazę danych?
Użytkownik Ferrari edytował ten post 12 lipiec 2007, 21:10


Czyli chcesz mieć po kolei wypisane kategorie, jakie trzeba przebrnąć, żeby dotrzeć do każdej z nich?

Jeżeli dobrze Cię zrozumiałem (a nie było to łatwe - na przyszłość dawaj kawałki kodu, a nie pisz wypracowań, przynajmniej imho), to masz drzewo, i próbujesz znaleźć jakiś cudny sposób na jego przejście...

Nie udało mi się zrozumieć jaki masz na to pomysł, ale chyba przyszło Ci na myśl coś takiego (i nic lepszego nad to nie znajdziesz).

A tak zupełnie poza tematem, to Ty się chłopie z tym webdevelopingiem marnujesz, z takim upiornym instynktem do kombinowania to się algorytmiką zainteresuj :D

jemu chodzi tylko o to, zeby wypluc kategorie i podkategorie drzewa wg kolejnosci bez wielu zapytan :) tak, zeby zrobic zwykle menu.
mozesz np dodac pole kolejnosc, w ktorym kazda kolejna cyfra oznacza kolejnosc w danej kategorii, np.
glowna1 - 1
-podrzedna -11
-inna podrzedna - 12
--podrzedna "innej podrzednej" - 121
-jeszcze inna podrzedna - 13
glowna2 - 2

itd
teraz wystarczy posortowac wg tego pola jako tekst (tak ze 1 jest przed 11 itd). dzieki temu mozesz tez kontrolowac kolejnosc kazdej pozycji w kategori. to ze w danym momencie zaczyna sie podrzedna kategoria rozpoznajesz po zwiekszajacej sie dlugosci tego pola z numerkiem :) jesli potrzebujesz wiecej niz 10 pozycji na kategorie to musisz rozdzielic te liczby jakims znakiem
Użytkownik Deadeye edytował ten post 13 lipiec 2007, 00:37
Nie mogę ograniczyć ilości podkategorii w kategoriach. Możliwym limitem ostatecznie może być ilość poziomów (do 10). Stosując powyższe rozwiązanie, trzeba będzie poprawić liczby.

Jeden ze sposobów: http://www.depesz.co...lementation.php
Wymaga jednak dużej ilości danych w dodatkowej tabeli - wszystkich powiązań. Jednak zmiana kategorii głównej bądź dodanie nowej subkategorii nie jest łatwe, ponieważ trzeba zmienić / dodać / usunąć powiązanie do wszystkich "rodziców".

Może dobrym sposobem jest zastosowanie pętli w SQL-u, aby pobrać wszystkie podkategorie danej kategorii? Macie jakieś pomysły? Najlepiej, żeby do wyniku dopisany był również poziom względem pobieranej kategorii (która może mieć wyższe kategorie). :)

Nie chcę już dodawać kolejnych pól do tabeli z danymi kategorii - jest ich aktualnie 11, a dojdzie jeszcze 1 (obraz kategorii).
Użytkownik Ferrari edytował ten post 13 lipiec 2007, 07:58


jesli dobrze zrozumiałem to przydało by sie napisać jakąś funkcję rekurencyjną...

przykładowo:

$data - tablica przechowujaca drzewo -> INDEKSY
$level - poziom drzewa

function generate_tree($level) { $data = array(); $sql = "SELECT id, parent_id FROM nazwa_tablicy WHERE parent_id=".$level" ORDER BY kolumna_do_sortowania ASC"; $result = mysql_query($sql); // wykonanie zapytania - przykładowo MySQL while($row = mysql_fetch_array($result, MYSQL_ASSOC)) { // sprawdzamy czy dany element ma "dzieci" $temp = generate_tree($row['id']); if(!empty($temp)) $data[$row['id']] = $temp; else $data[$row['id']] = ''; } return $data; }

mam nadzieje ze ni nakomplikowalem...

oczywiscie odpalasz funkcje z argumentem $level = 0
krotki opis:

1. odpalamy funkcje z adgumentem 0
2. zapytanie do bazy o wszystkie pozycje z argumentem 0
3. dla kazdego z rekordow sprawdzamy czy ma on dzieci -> wywołanie tej samej funkcji dla rekordu -> !empty($temp)
4. zagłebiamy sie przez całą przeszukiwaną strukturę

mam nadzieje ze to pomoże... ;)
PZDR

@mrbungle: W ten sposób wykonujemy tyle zapytań, ile jest poziomów - podobne rozwiązanie nie jest złe przy małej ilości poziomów (1-3). Jednak zawodzi przy wyświetlaniu wszystkich kategorii. Wolałbym, aby SQL już wszystko posortował i dodał poziom do wyniku. Jeśli nic nie wymyślicie, będę próbował ułożyć pętlę.

Czy SQLite w ogóle obsługuje pętle (nic na ten temat nie znalazłem na oficjalnej stronie)? Jeśli nie, można utworzyć 2 wersje - z tym, że dla SQLite kategorie nie będą pogrupowane.
Użytkownik Ferrari edytował ten post 13 lipiec 2007, 11:24

@mrbungle: W ten sposób wykonujemy tyle zapytań, ile jest poziomów - podobne rozwiązanie nie jest złe przy małej ilości poziomów (1-3). Jednak zawodzi przy wyświetlaniu wszystkich kategorii.

masz to udowodnione? jakiś czas generacji elementów?

SQLem mozna ale chyba nie w MySQLu :( ale w tym już Ci nie pomoge :(


Nie mogę ograniczyć ilości podkategorii w kategoriach. Możliwym limitem ostatecznie może być ilość poziomów (do 10). Stosując powyższe rozwiązanie, trzeba będzie poprawić liczby.
nie trzeba - wystarczy oddzielic np. kropka i dodac 0
001
002.001
012.033.001
zasada dzialania ta sama.
Użytkownik Deadeye edytował ten post 13 lipiec 2007, 13:27
Analiza rozwiązania Deadeye

1. Odczyt wszystkich:
a) sortowanie - nowe pole, nazwa
b) poziom - liczyć ilość kropek, czy inaczej?

2. Nowa kategoria:
a) nowe pole - chyba wystarczy dopisanie dodatkowej cyfry, bo sortujemy też wg nazwy

3. Podkategorie 1 kategorii:
a) warunek - ? (nadk = $id wyciągnie podkategorie 1 niższego poziomu, a tylko na nowym polu nie można polegać)

Problem pozostaje nadal, gdyż w ostatnim przypadku nie znamy ID niższych kategorii. Albo trzeba wykonać kilka zapytań, albo wykorzystać pętlę - a wtedy nowe pole nie będzie potrzebne.

Językowi SQL i silnikom baz danych brakuje funkcjonalności, m.in.:
- zbioru wartości bądź liczb jako typu danych (coś podobnego do listy w Logo), np. [5,2,8,9]
- funkcji ułatwiających pobieranie gałęzi drzewa - jednym ze sposobów jest określenie wyższej kategorii w polu X, ewentualnie niższych (jako zbiór liczb).

Wracając do problemu, najlepiej chyba rozwiązać go przy pomocy pętli w SQL. Czy macie jakieś pomysły?
Użytkownik Ferrari edytował ten post 13 lipiec 2007, 14:02

Analiza rozwiązania Deadeye

1. Odczyt wszystkich:
a) sortowanie - nowe pole, nazwa
B) poziom - liczyć ilość kropek, czy inaczej?

2. Nowa kategoria:
a) nowe pole - chyba wystarczy dopisanie dodatkowej cyfry, bo sortujemy też wg nazwy

3. Podkategorie 1 kategorii:
a) warunek - ? (nadk = $id wyciągnie podkategorie 1 niższego poziomu, a tylko na nowym polu nie można polegać)

nie za bardzo jestem teraz w stanie zrozumiec o co ci chodzi, ale po prostu pobierasz kolejne wyniki i przy kazdym sprawdzasz dlugosc pola z kolejnoscia czyms w stylu
strlen(akt_kolejnosc) > strlen(poprzednia_kolejnosc) ? teraz_podkategorie() : strlen(akt_kolejnosc) < strlen(poprzednia_kolejnosc) ? powrot_do_wypisywania_nadkategorii() : ten_sam_poziom_kategorii();

dzieki temu nie musisz uzywac zadnych dodatkowych powiazan nadrzedny-podrzednyl, bo dzieki sortowaniu + porownaniu dlugosci mozesz latwo odtworzyc cala strukture. sortujesz oczywiscie tylko wg pola "kolejnosc" ktore powinno byc unikatowe. jesli dodajesz nowa kategorie, to znajdujesz ostatnia pozycje o odpowiedniej dlugosci (dla glownych kategorii bedzie to 3, dla podkategorii 7, dla kolejnych 11,15 itd.). a jesli chcesz dodac podkategorie danej kategorii, to pobierasz jej kolejnosc, sprawdzasz czy istnieje kategoria z 4 znakami dluzszymi niz ta do ktorej dodajesz, z ktorych wszystko oprocz tych 4 ostatnich sa takie same jak w tej do ktorej dodajesz, no i w zaleznosci czy to istnieje taka (dodajesz kolejna podkategorie na tym poziomie - znajduejesz ostatnia, zwiekszasz liczbe o jeden) czy nie (do nadkategorii dodajesz '.001') wykonujesz odpowiednia akcje
Użytkownik Deadeye edytował ten post 14 lipiec 2007, 00:09
No chyba że w zapytaniu SQL zastosuję coś w stylu: nowe_pole = substr($id,0,3) //wersja PHP, ale MySQL powinien mieć odpowiednik - wtedy powinno pobrać podkategorie kategorii.

Co do pobierania nadkategorii kategorii np. o indeksie 005.007.009 - jednym ze sposobów jest rzutowanie typu danych na (int) w PHP, zapisanie ID do tablicy i pobranie rekordów z bazy. Coś mi się tu jednak nie podoba. W indeksach muszą się znajdować ID podkategorii, prawda? Skąd więc tam jedynka? Chyba, że ostatni człon "adresu IP" to tylko indeks kolejności.

Poczekam jeszcze z implementacją. Równie dobrze mogę zastosować metodę przemierzania drzewa ("tree traversal") znaną też jako "nested tree", sposób Depesza (wszystkie powiązania), bądź obecne (lecz z zastosowaniem pętli w SQL-u).


Co do pobierania nadkategorii kategorii np. o indeksie 005.007.009 - jednym ze sposobów jest rzutowanie typu danych na (int) w PHP, zapisanie ID do tablicy i pobranie rekordów z bazy.
nawet nie trzeba - obcinasz 4 ostatnie znaki, i juz masz wartosc pola kolejnosc w nadkategorii.


Coś mi się tu jednak nie podoba. W indeksach muszą się znajdować ID podkategorii, prawda? Skąd więc tam jedynka? Chyba, że ostatni człon "adresu IP" to tylko indeks kolejności.
w jakich indeksach? w polu kolejnosc ostatni czlon wg mojej implementacji to tylko kolejnosc danej pozycji na danej glebokosci (gdy na danym poziomie masz tylko jedna podkategorie to zawsze bedzie miala ona koncowke .001).
Użytkownik Deadeye edytował ten post 14 lipiec 2007, 11:41

to tylko kolejnosc danej pozycji O to mi chodziło. Może nawet pole "wyższa kategoria" jest niepotrzebne?

Niedługo zacznę implementować ten sposób, bo jest chyba najlepszy do tych celów. Wtedy okaże się, czy nie ma innych haków.

Nie wiem czy o tym juz myślałeś,czy może właśnie tak masz (niewiele zrozumiałem z tego wszystkiego),ale może mój sposob się przyda (być może nie jest najszybszy).
W bazie danych mógłbyś umieścić pole ID oraz subID. W polu ID są przechowywane identyfikatory z auto_increment,natomiast subID to identyfikatory przypisujące. 0 jest dla głównych (auto_increment rozpoczyna od 1),natomiast kolejne by były dla podmenu.
Pobierane by były wszystkie rekordy (jeżeli o to chodzi) i sortowane według subID. W
$dane = mysql_fetch_assoc($result)uzyskujesz$lista[$dane['subID' ]] = $daneTeraz czas na pętlę.$pomocnicza = 0; foreach ($lista as $k => $v) { if ($pomocnicza > $k) // otwiera podmenu // wyświetla menu if ($pomocnicza < $k) // zamyka podmenu $pomocnicza = $k; }Mam nadzieję,że o to chodziło oraz że kod działa bo nie sprawdzałem.


O to mi chodziło. Może nawet pole "wyższa kategoria" jest niepotrzebne?
tak, w przy tym sposobie pole kolejnosc wystarcza, choc wyzsza kategoria upraszcza niektore zapytania.

Jest jeszcze taka sprawa, że kategoria, której nadkategorie bądź podkategorie chcemy wyświetlić, może być dowolnego poziomu. Jak wtedy wyszukam w bazie odpowiednie rekordy? Muszę użyć funkcji, która wyszuka 001 bądź 010 w nowym polu. Coś w stylu substr() odpada. Pozostaje operator LIKE lub inna funkcja. Nie wiem, czy sposób będzie aż tak wydajny - bo chyba nie bardzo.

Rozważam jeszcze metodę Depesza.
Użytkownik Ferrari edytował ten post 15 lipiec 2007, 12:30

Jest jeszcze taka sprawa, że kategoria, której nadkategorie bądź podkategorie chcemy wyświetlić, może być dowolnego poziomu. Jak wtedy wyszukam w bazie odpowiednie rekordy? Muszę użyć funkcji, która wyszuka 001 bądź 010 w nowym polu. Coś w stylu substr() odpada. Pozostaje operator LIKE lub inna funkcja. Nie wiem, czy sposób będzie aż tak wydajny - bo chyba nie bardzo.
like wyszukujace wedlug poczatku wyrazenia (np 010_% ) dziala dosc szybko i nie jest jakims olbrzymim obciazeniem dla bazy

Na razie spróbuję zastosować sposób Depesza. Więcej wpisów w bazie, lecz prawdopodobnie lepiej się zorientować, co i jak. Jeśli będą problemy, wybiorę inny (np. IP), o którym pisał Deadeye.

29 lipca: Tak właściwie jeszcze myślę, czy stosowanie podobnego systemu ma większy sens. O ile pobranie drzewa w odpowiedniej kolejności jest szybkie (choć nie zawsze), dodanie lub aktualizacja kategorii wymaga skomplikowanych zapytań.

http://www.depesz.co...lementation.php
Zacząłem stosować tą metodę. Pomińmy już aktualizację (autor podał na grupach gotowe rozwiązanie na tacy dla MySQL) - można ewentualnie też użyć pętli (podobnie do rekurencji, co stosuję np. do pobrania nadkategorii). Załóżmy, że chcemy pobrać całe drzewo posortowane wg nazwy i pogrupowane. Próby:
- name, depth (nic z tego)
- level, name (najpierw 1-poziom, potem 2-gi, itp. - też źle)
- parent_id, depth, name (już prędzej tak - 3 pola w ORDER BY - o ile osiągniemy wynik, jaki chcemy)
Spójrzcie na rozwiązanie na ww. stronie - użyta procedura SQL i dodatkowe pole.

Rozważam też przechowywanie podkategorii przy każdej kategorii lub przejście tylko na słowa kluczowe. W pierwszym przypadku oprócz zalety, że dozwolone jest posiadanie wielu "rodziców", można zapomnieć o pobieraniu nadkategorii, a w drugim - brak hierarchii.

Może metoda nested tree (tree traversal) nie jest wcale taka zła? Muszę się jeszcze zastanowić. Wg tego artykułu utworzenie listy nadkategorii jest możliwe przy 1 zapytaniu.

Ostatecznie zostaje kombinacja tych metod bądź zostanie tylko przy ID i parent_id :) Gdyby tylko SQLite obsługiwał pętle, może nie myślałbym nad zastosowaniem systemu drzew.

- - - - 31 lipca - - - -

Użyłem metody IP trees, jednak nie można w 1 polu zawrzeć ID i kolejności. Na początku miało być tak:
- pole path: ID1.ID2.kolejność
- pole sc: nadkategoria
Okazało się, że sortowanie rekordy sortowane są nieprawidłowo. Powód - wymieszanie ID z kolejnością.

Jest na to rozwiązanie. Pole PATH może zawierać tylko kolejności. Zdaje mi się, że tak również da się poprawnie zaktualizować wartość pola w całej gałęzi. Co do pobierania nadkategorii - tu będzie problem. Pobranie wszystkich subkategorii nie sprawi kłopotu. Zagrożenie - jeżeli edytujesz kategorię, a kto inny zmieni jej kolejność, możesz zmienić wartość innym kategoriom.

Alternatywne rozwiązanie (które chyba zastosuję) - pole PATH zawiera ID nadkategorii (np. 10.15). Kategorie będą sortowane po PATH i po nazwie (choć tu też mogą być problemy, bo co mają zawierać główne kategorie w polu PATH?*). Ewentualnie dodać nowe pole "kolejność".

* Powiedzmy, że wyciągamy posortowane i pogrupowane kategorie. W głównych kat. PATH wynosi 0 bądź ''. I co? Główne kategorie nie mają wyższego ID. Ewentualnie mogą zawierać tam swój ID, choć nie wiem, czy to tylko nie spowoduje innych konfliktów.

Co o tym myślicie? A może jestem w błędzie i zmieszanie nadkategorii z kolejnością jest możliwe?
Użytkownik Ferrari edytował ten post 31 lipiec 2007, 22:32
  • zanotowane.pl
  • doc.pisz.pl
  • pdf.pisz.pl
  • nvm.keep.pl

  • Sitedesign by AltusUmbrae.