Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Nested Sets - przenoszenie węzłów i liści góra/dół
Forum PHP.pl > Forum > PHP
grz16w
Witam.

Czy ktoś wie lub posiada przykład działającej poprawnie funkcji przesuwania liści i węzłów w strukturze Nested Sets wewnątrz jednej gałęzi (rodzica)? Mam tabelę posiadającą kolumny `id`, `nazwa`, `lft`, `rgt`, `parent`. W jaki sposób zamienić miejscami dwa węzły lub liście wewnątrz tego samego rodzica (uwzględniając, że te węzły mogą posiadać również swoje dzieci itd.)? Z góry dziękuję serdecznie za odpowiedzi smile.gif
Crozin
Zanim zapomnę: o ile dobrze pamiętam phpBB3 albo jakiś inny PHP-owski skrypt forum ma drzewo kategorii oparte o ten właśnie model i jest tam kod do przenoszenia - możesz podejrzeć.

Ogólnie to jest to trochę... zamotane. Bo trzeba zmieniać klucze LFT i RGT w połowie drzewa przy próbie przeniesienia jakiegoś węzła. Sprawy nie ułatwia fakt, że właściwie każdy rodzaj czy nawet wariant operacji wymaga nieco innego podejścia.

Przede wszystkim musisz wyliczyć deltę, czyli fragment drzewa który przenosisz (różnica RGT i LFT). Następnie musisz pomniejszyć indeksy LFT / RGT wszystkich elementów występujących po przenoszonej gałęzi właśnie o wartość delty. Teraz drzewo ponownie jest spójne - nie ma żadnych "dziur". Kolejny krok to zrobienie miejsca na przenoszoną gałąź. Musisz indeksy LFT / RGT wszystkich elementów które będą miały występować po wklejanym elemencie zwiększyć o wartość delty. Ostatni krok to zmienienie indeksów LFT / RGT przenoszonej gałęzi tak by pasowały do swojej nowej lokacji.

I jeszcze kilka rad:
1. W przypadku nested set warto również przechowywać w bazie informacje o poziomie zagłębienia konkretnego elementu - bardzo ułatwia to wiele operacji.
2. Całość obejmij transakcją.
3. Mimo iż da się to skrócić czasami do mniejszej ilości ale bardziej skomplikowanych zapytań nie rób tego. 10 prostych UPDATE-ów jest po stokroć lepsze niż 2 skomplikowane, pełne CASE-ów i innych paskudnych konstrukcji.
jang
http://www.phpriot.com/articles/nested-trees-2/7

Odrobinę trzeba przerobić bo klasa jest pod postgree ale z tego co pamiętam to działało całkiem nieżle.
Za pomocą dodatkowego zapytania zmieniasz wartość "parent_id" czyli przenosisz liścia , gałąź w inne miejsce. Następnie wywołujesz metodę rebuild() i masz nowe drzewko smile.gif
JoShiMa
Cytat(grz16w @ 10.04.2011, 22:42:12 ) *
Witam.

Czy ktoś wie lub posiada przykład działającej poprawnie funkcji przesuwania liści i węzłów w strukturze Nested Sets wewnątrz jednej gałęzi (rodzica)?


Czytałeś to: przenoszenie gałęzi ?
grz16w
Cytat(JoShiMa @ 11.04.2011, 11:24:51 ) *
Czytałeś to: przenoszenie gałęzi ?

tak, czytałem i w podobny sposób zrobiłem inne funkcje, czyli przenoszenie gałęzi, a nie zamiana miejscami (wewnątrz jednej gałęzi nadrzędnej). Nie wiedziałem jak z tego to drugie wykombinować smile.gif

Crozin, wielkie dzięki, świetny opis, już się biorę do roboty smile.gif mam nadzieję że się uda bo tylko ta funkcja mi została do opracowania smile.gif
JoShiMa
Cytat(grz16w @ 11.04.2011, 15:13:30 ) *
tak, czytałem i w podobny sposób zrobiłem inne funkcje, czyli przenoszenie gałęzi, a nie zamiana miejscami (wewnątrz jednej gałęzi nadrzędnej). Nie wiedziałem jak z tego to drugie wykombinować smile.gif


Ale to nie ma znaczenia gdzie przenosisz. Istotą jest tylko żeby prawidłowo obliczyć miejsce.
grz16w
Cytat(JoShiMa @ 11.04.2011, 22:38:11 ) *
Ale to nie ma znaczenia gdzie przenosisz. Istotą jest tylko żeby prawidłowo obliczyć miejsce.


Mój błąd, masz rację, właśnie skończyłem tą opcję i faktycznie to tak funkcjonuje wstydnis.gif a korzystając z okazji miałbym jeszcze jedno pytanko. Czy jest możliwość obliczenia "głębokości" danego węzła/liścia mając do dyspozycji jedynie pola `lft`, `rgt`, `parent` bez możliwości stworzenia dodatkowej kolumny np. `depth`? smile.gif
Crozin
Można ale jest to dosyć skomplikowane, a trzymanie tego w formie statycznej nie sprawia wielu problemów przy modyfikacji drzewa. A warto to mieć o czym przekonasz się chociażby w w momencie gdy będziesz chciał wybrać przykładowo tylko pierwszy poziom zagłębienia itp.
JoShiMa
Cytat(grz16w @ 11.04.2011, 22:02:31 ) *
Mój błąd, masz rację, właśnie skończyłem tą opcję i faktycznie to tak funkcjonuje wstydnis.gif a korzystając z okazji miałbym jeszcze jedno pytanko. Czy jest możliwość obliczenia "głębokości" danego węzła/liścia mając do dyspozycji jedynie pola `lft`, `rgt`, `parent` bez możliwości stworzenia dodatkowej kolumny np. `depth`? smile.gif

Oczywiście i nawet parent nie jest potrzebne i to wcale nie jest skomplikowane (Crozin opowiada bzdury). W cyklu artykułów z którego jeden Ci podesłałam jest to pokazane. Chyba w trzecim z serii. W jednym niezbyt skomplikowanym zapytaniu możesz wyciągnąć dowolną gałąź i od razu policzyć depth dla wszystkich jej elementów/ Dodając w takim zapytaniu klauzlę HAVING wyciąg asię tylko wskazany poziom. Bułka z masłem.

--- EDIT ---

Tak czysto logicznie. Wystarczy policzyć ile elementów ma jednocześnie lft mniejszy i rgl większy od tego, który badasz. Policzysz w ten sposób wszystkie elementy, które okalają wskazany a więc sa dla niego nadrzędne. Proste, nie?

@Crozin naucz się wreszcie tego, no nie trudne a może przestaniesz straszyć ludzi.
Crozin
@JoShiMa: Szkoda tylko, że nie dodałeś że dynamiczne obliczanie zagłębienia wymaga dodatkowego złączenia (w dodatku w jednym z gorszych wariantów), HAVING też do demonów szybkości nie należy. No i trzeba powtarzać ten bezsensowny krok za każdym razem, gdy chcemy skorzystać z tej kolumny.

Przy 1 000 rekordów to bez znaczenia, ale przy 500 000 czy 50 mln sprawa wygląda trochę inaczej.
JoShiMa
Cytat(Crozin @ 11.04.2011, 22:59:11 ) *
@JoShiMa: Szkoda tylko, że nie dodałeś że dynamiczne obliczanie zagłębienia wymaga dodatkowego złączenia

Lub podzapytania. I nie odwracaj kota ogonem. Pisałeś, że to wyjątkowo trudne a ja twierdzę, że bułka z masłem.

Cytat(Crozin @ 11.04.2011, 22:59:11 ) *
(w dodatku w jednym z gorszych wariantów), HAVING też do demonów szybkości nie należy. No i trzeba powtarzać ten bezsensowny krok za każdym razem, gdy chcemy skorzystać z tej kolumny.

Jeszcze bardziej bezsensowne jest przeliczanie kolumny ilekroć chcemy przenieść element lub gałąź.

Nie musisz lubić nested-set, bo jak wszystko mają swoje wady. Fajnie jednak by było, gdybyś nie wprowadzał w związku z tym ludzi w błąd pisząc, że to koszmarnie trudne.

Crozin
Cytat
Lub podzapytania. I nie odwracaj kota ogonem. Pisałeś, że to wyjątkowo trudne a ja twierdzę, że bułka z masłem.
To podzapytanie to o ile się nie mylę (już trochę późno jest) jeszcze gorszy pomysł. I nigdzie nie napisałem, że jest to "wyjątkowo trudne", tylko "dosyć skomplikowane" bo jest*. Programiście utrudnia prace, bo każdorazowo musi zaśmiecać treść zapytania fragmentem do wyliczenia tej wartości, a silnik bazy danych wręcz katowany jest jeżeli mamy do czynienia z jakimś sensowniejszym zestawem danych.
Cytat
Jeszcze bardziej bezsensowne jest przeliczanie kolumny ilekroć chcemy przenieść element lub gałąź.
Uznam, że po prostu z rozpędu już to palnąłeś. Bo właśnie napisałeś, że jednokrotne wyliczenie wartości i zapisanie jej jest bardziej bezsensowne niż każdorazowe wyliczenie tej samej wartości. Może jeszcze miałoby to sens w przypadku gdyby owa wyliczana wartość zajmowała masę miejsca, ale tutaj mowa jest o zwykłej liczbie całkowitej.
Cytat
Nie musisz lubić nested-set, bo jak wszystko mają swoje wady. Fajnie jednak by było, gdybyś nie wprowadzał w związku z tym ludzi w błąd pisząc, że to koszmarnie trudne.
A gdzie napisałem, że nie lubię? Zresztą jak można nie lubić "algorytmu"? I nie wciskaj mi słów których nigdy nie napisałem (patrz: koszmarnie trudne) bo takie coś jest po prostu bezczelne.

PS. Nie wiem czy zauważyłeś, ale kłócisz się (bo tak to zaczynam odbierać) o straszną pierdołę. O to, że zasugerowałem przechowywanie wyniku jednej operacji na stałe w bazie.

* Dobra, może "skomplikowane" to złe słowo tutaj - ale "niewygodne" pasuje idealnie.
JoShiMa
Cytat(Crozin @ 11.04.2011, 23:32:32 ) *
To podzapytanie to o ile się nie mylę (już trochę późno jest) jeszcze gorszy pomysł.

Wręcz przeciwnie. Przy większych bazach jest szybsze.



Cytat(Crozin @ 11.04.2011, 23:32:32 ) *
I nigdzie nie napisałem, że jest to "wyjątkowo trudne", tylko "dosyć skomplikowane" bo jest*. Programiście utrudnia prace, bo każdorazowo musi zaśmiecać treść zapytania fragmentem do wyliczenia tej wartości

Za to przy Twoim sposobie każdorazowo musi zaśmiecać kod wyliczeniami nowych wartości depth przy przenoszeniu gałęzi. Przenosząc gałąź do innej lokalizacji musisz obliczyć depth dla tej lokalizacji. różnicę między wartością bieżącą a doelową i przeliczyć depth wszystkich elementów przenoszonej gałęzi. Rzeczywiście. Mniej skomplikowane. No sorry ale nie przekonasz mnie, że to jest jakiekolwiek ułatwienie. Nie wiem czemu ale odnoszę wrażenie, że znasz zagadnienie jedynie z teorii.

Aha. Wolałabym, żebyś się do mnie zwracał w rodzaju żeńskim.
wiewiorek
Joshima napisal: "@Crozin naucz się wreszcie tego, no nie trudne a może przestaniesz straszyć ludzi. / Pisałeś, że to wyjątkowo trudne a ja twierdzę, że bułka z masłem. "

Uwielbiam takich ludzi jak Joshima - mowia, ze cos jest latwe ale kodu nie podadza - jak to mowi moja babcie "g*** widzieli, g*** umieja" tongue.gif

Nested set z pewnością są SKOMPLIKOWANE !

grz16w więc co tam chciałeś ? przenoszenie gałęzi - więc to będzie tak:
zakładając, że mamy tabelę nested_category z kolumnami:
category_id
name
lft
rgt


Kod do przenoszenia poddrzewa:
  1. --przenoszenie poddrzewa np. z category_id = 2 i podczepienie go do np. category_id = 6:
  2. START TRANSACTION;
  3.  
  4. --jesli przenoszone poddrzewo ma trafic na poczatek:
  5. --SELECT @destination := (lft + 1) FROM nested_category WHERE category_id = 6;
  6. --jesli przenoszone poddrzewo ma trafic na koniec:
  7. SELECT @destination := rgt FROM nested_category WHERE category_id = 6;
  8. SELECT @cat_a_width := ((rgt - lft) + 1) FROM nested_category WHERE category_id = 2;
  9.  
  10. UPDATE nested_category SET rgt = rgt + @cat_a_width WHERE rgt >= @destination;
  11. UPDATE nested_category SET lft = lft + @cat_a_width WHERE lft >= @destination;
  12.  
  13. SELECT @cat_a_lft := lft, @cat_a_rgt := rgt FROM nested_category WHERE category_id = 2;
  14. SELECT @diff := @destination - @cat_a_lft;
  15.  
  16. UPDATE nested_category SET rgt = rgt + @diff WHERE rgt BETWEEN @cat_a_lft AND @cat_a_rgt;
  17. UPDATE nested_category SET lft = lft + @diff WHERE lft BETWEEN @cat_a_lft AND @cat_a_rgt;
  18.  
  19. UPDATE nested_category SET rgt = rgt - @cat_a_width WHERE rgt >= @cat_a_lft;
  20. UPDATE nested_category SET lft = lft - @cat_a_width WHERE lft >= @cat_a_lft;
  21.  
  22. COMMIT;



Glebokosc elementow drzewa daje:
  1. SELECT node.name, (COUNT(parent.category_id) - 1) AS depth
  2. FROM nested_category AS node,
  3. nested_category AS parent
  4. WHERE node.lft BETWEEN parent.lft AND parent.rgt
  5. GROUP BY node.category_id
  6. ORDER BY node.lft;



Ale tak jak mowil Crozin - ZDECYDOWANIE daj kolumne depth i parent_id - zobaczysz ze to w wielu sytuacjach ulatwi zapytania. A i jeszcze dodam, ze sam zawsze sie waham czy uzywac nested set czy adjacency list, ale mysle ze warto wziac pod uwage, ze w wiekszosci wypadkow uzywana bywa adjacency list czyli sposob z sama kolumna parent_id i nie wplywa to w zauwazalny sposob na czas oczekiwania na wyswietlenie strony - jesli rekordow jest bardzo duzo to wowczas stosuje sie cachowanie.
JoShiMa
Cytat(wiewiorek @ 12.04.2011, 07:55:05 ) *
Uwielbiam takich ludzi jak Joshima - mowia, ze cos jest latwe ale kodu nie podadza - jak to mowi moja babcie "g*** widzieli, g*** umieja" tongue.gif

Podałam linka do serii artykułów w których pokazane są i wyjaśnione wszystkie zapytania. Nieskromnie dodam, że sama to pisałam. Więc nie mów mi, że kodu nie podałam. Trzeba tylko uważnie czytać cały wątek.
grz16w
Więc tak.. Użyłem metody:

Cytat
SELECT node.name, (COUNT(parent.category_id) - 1) AS depth
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.category_id
ORDER BY node.lft;


jednak nie wiem jak z tego wykombinować głębokość konkretnego elementu. To zapytanie wyświetla mi jedynie tablicę, w której jest tylko pierwszy rekord z bazy i jego zagłębienie. Jak dostać się do pozostałych elementów? Od razu mówię że troszkę jestem zielony jeżeli chodzi o SQL wstydnis.gif
JoShiMa
A to zapytanie wywołujesz w bazie czy w skrypcie PHP? Powinno Ci wyrzucić wszystkie elementy jesli masz prawidłowo skonstruowane drzewo i wszystkie elementy lft rgt mają prawidłowe wartości.Pokaż zawartość tabeli.
grz16w
Wykonuję to zapytanie przez PHP. Gdy wykonuję w panelu phpMyAdmin wyświetla poprawnie tabelę nazw z przypisanymi wartościami zagłębienia, natomiast poprzez PHP już nie.
Kod
$zapytanie = "
SELECT node.nazwa, (COUNT(parent.id) - 1) AS depth
FROM kategorie AS node,
kategorie AS parent
WHERE node.lewa BETWEEN parent.lewa AND parent.prawa
GROUP BY node.id
ORDER BY node.lewa;
";

$sql = mysql_query($zapytanie);
$sql = mysql_fetch_assoc($sql);
print_r($sql);


po tym wyświetla mi tablicę tylko z pierwszym elementem z bazy...

Kod
Array
(
    [nazwa] => kat1
    [depth] => 0
)


zrzuty z phpMyAdmin:



Crozin
Cytat
Cytat
To podzapytanie to o ile się nie mylę (już trochę późno jest) jeszcze gorszy pomysł.
Wręcz przeciwnie. Przy większych bazach jest szybsze.
Aż zrobię testy jutro czy pojutrze jak znajdę chwilę czasu. Wyniki oczywiście udostępnię.
Cytat
Za to przy Twoim sposobie każdorazowo musi zaśmiecać kod wyliczeniami nowych wartości depth przy przenoszeniu gałęzi. Przenosząc gałąź do innej lokalizacji musisz obliczyć depth dla tej lokalizacji. różnicę między wartością bieżącą a doelową i przeliczyć depth wszystkich elementów przenoszonej gałęzi. Rzeczywiście. Mniej skomplikowane. No sorry ale nie przekonasz mnie, że to jest jakiekolwiek ułatwienie.
Na dobrą sprawę to jedyne co trzeba zrobić to:
  1. UPDATE ... SET depth = depth + 123 WHERE ...;
Gdzie 123 to różnica dla korzenia przenoszonej gałęzi. WHERE określa jeszcze po prostu całą przenoszoną gałąź.
Cytat
Nie wiem czemu ale odnoszę wrażenie, że znasz zagadnienie jedynie z teorii.
Nie wiem czemu ale odnoszę wrażenie, że jest dokładnie odwrotnie albo doszło tutaj do jakiegoś nieporozumienia między nami. wink.gif

@grz16w: mysql_fetch_array zwraca kolejny (jeden) wynik z pobranych. W manualu masz przykład użycia.

Cytat
Nie wiem czemu ale odnoszę wrażenie, że znasz zagadnienie jedynie z teorii.
Przepraszam, przyzwyczajenie. wink.gif
JoShiMa
Cytat(grz16w @ 12.04.2011, 11:27:00 ) *
$sql = mysql_query($zapytanie);
$sql = mysql_fetch_assoc($sql);
print_r($sql);[/code]

po tym wyświetla mi tablicę tylko z pierwszym elementem z bazy...


No bo pobierasz tylko jeden rekord. mysql_fetch_assoc($sql); ma latać w pętli tongue.gif

@Crozin, znów oszukujesz. Najpierw trzeba obliczyć depth dla miejsca docelowego i depth węzła który przenosisz. Dopiero potem możesz obliczyć różnicę i wykonać zapytanie, które pokazałeś. Niby drobiazgi, ale jednak tongue.gif
Crozin
Nic nie oszukuję. Przecież przy przenoszeniu i tak wcześniej pobiera się informacje o przenoszonej gałęzi i jej docelowym miejscu, więc te dane mamy dostępne na tacy.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.