Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: pytanie o nested set
Forum PHP.pl > Forum > PHP
wiewiorek
Jak mamy w bazie danych tabelę z kolumnami:
id | id_parent | order
To dodając nowy element użytkownik wybiera z select listy rodzica oraz z drugiej select listy liczbę reprezentującą wagę/kolejność dodawanego elementu w stosunku do innych na tym samym poziomie.

A jak mamy w bazie danych tabelę ze strukturą charakterystyczną dla nested set:
id | left | right
To użytkownik dodając nowy element wybiera z select listy rodzica, ale w jaki sposób może ustalić wagę/kolejność nowo dodawanego elementu w stosunku do innych na tym samym poziomie ? Nie bardzo wiem jak umożliwić użytkownikom ustalenie kolejności.


Zyx
Przecież left i right dzieci tego samego węzła są ułożone w kolejności, w jakiej te elementy występują:

Kod
SELECT ... ORDER BY `left`
wiewiorek
No dobra, ale jak rodzic ma dwójkę potomków a ja chcę wstawić miedzy nie jeszcze jednego potomka albo przed nimi, albo po nich to w jaki sposób mam to umożliwić użytkownikowi ?
Crozin
Musisz odpowiednio pozmieniać wartości left/right sporej części drzewa. W sieci masz mnóstwo przykładów na to jak przemieszczać poszczególne gałęzie w takim modelu drzewka. google: moving nested set, move nested set - czy cokolwiek zwierającego "nested set" i coś związanego z ruchem.
wiewiorek
Tylko takie przenoszenie gałęzi oznacza wielokrotny update co moze prowadzic do anomalii.

everth
A gdzie jest napisane że relacyjne bazy danych najlepiej nadają do tworzenia drzew? Jeśli rzeczywiście chcesz ustabilizować sobie strukturę drzewa to napisz jakiś wyzwalacz który po dodaniu dziecka uaktualni strukturę drzewa - bo wysyłanie kilkunastu zapytań w tym celu mija się z celem smile.gif
erix
Cytat
gałęzi oznacza wielokrotny update co moze prowadzic do anomalii.

Blokowanie tabeli albo transakcja.
Crozin
Cytat
Tylko takie przenoszenie gałęzi oznacza wielokrotny update co moze prowadzic do anomalii.
Nie. Oznacza to jednokrotny update i insert. Ale jak już erix zauważył: transakcje.
wiewiorek
A ma ktos gotowe zapytania do przenoszenia poddrzewa ? Bo cos nie do konca mi dziala ten skrypt do przenoszenia galezi:
  1. --tabela:
  2. CREATE TABLE nested_category (
  3. category_id INT AUTO_INCREMENT PRIMARY KEY,
  4. name VARCHAR(20) NOT NULL,
  5. lft INT NOT NULL,
  6. rgt INT NOT NULL
  7. );
  8.  
  9. INSERT INTO nested_category
  10. VALUES(1,'ELECTRONICS',1,20),(2,'TELEVISIONS',2,9),(3,'TUBE',3,4),
  11. (4,'LCD',5,6),(5,'PLASMA',7,8),(6,'PORTABLE ELECTRONICS',10,19),
  12. (7,'MP3 PLAYERS',11,14),(8,'FLASH',12,13),
  13. (9,'CD PLAYERS',15,16),(10,'2 WAY RADIOS',17,18);


  1. --skrypt do przenoszenia galezi z categori_id = 2 do category_id = 6:
  2. LOCK TABLE nested_category WRITE;
  3.  
  4.  
  5. SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
  6. FROM nested_category
  7. WHERE category_id = 2;
  8.  
  9. SELECT @newMyRight := rgt
  10. FROM nested_category
  11. WHERE category_id = 6;
  12.  
  13.  
  14. UPDATE nested_category
  15. SET lft = 0-lft, rgt = 0-rgt
  16. WHERE lft >= @myLeft AND rgt <= @myRight;
  17.  
  18.  
  19. UPDATE nested_category
  20. SET lft = lft - @myWidth
  21. WHERE lft > @myRight;
  22.  
  23. UPDATE nested_category
  24. SET rgt = rgt - @myWidth
  25. WHERE rgt > @myRight;
  26.  
  27.  
  28. UPDATE nested_category
  29. SET lft = lft + @node_size
  30. WHERE lft >= IF(@newMyRight > @myRight, @newMyRight - @myWidth, @newMyRight);
  31.  
  32. UPDATE nested_category
  33. SET rgt = rgt + @node_size
  34. WHERE rgt >= IF(@newMyRight > @myRight, @newMyRight - @myWidth, @newMyRight);
  35.  
  36.  
  37. UPDATE nested_category
  38. SET
  39. lft = 0-lft+IF(@newMyRight > @myRight, @newMyRight - @myRight - 1, @newMyRight - @myRight - 1 + @myWidth),
  40. rgt = 0-rgt+IF(@newMyRight > @myRight, @newMyRight - @myRight - 1, @newMyRight - @myRight - 1 + @myWidth)
  41. WHERE lft <= 0-@myLeft AND rgt >= 0-@myRight;
  42.  
  43.  
  44. UNLOCK TABLES;
everth
@wiewiorek - prawdopodobnie (nie testowałem dogłębnie tych procedur) poniżej masz kod do przenoszenia gałęzi.
Zapoznałem się bliżej z nested set (nie wiedziałem że istnieje taki model drzewa w sql winksmiley.jpg i wychodzi na to że jest to bardzo fajna rzecz. Poprzednicy mówili o stosowaniu transakcji - zgadzam się, jednak jeśli tylko użytkownik ma możliwość tworzenia procedur to zarządzanie tym drzewem staje się bajecznie proste. Niestety w tym przypadku nie możemy użyć wyzwalaczy (może gdyby pokombinować, nie wiem).

Przy budowaniu procedur korzystałem z gotowców z tej strony oraz tego rozwiązania w przypadku przenoszenia gałęzi.

Templatka z procedurami była przygotowywana dla tabeli "nested_category", dla innych trzeba przepisać nazwę. Tak wygląda użycie:
  1. CALL MOVE_BRANCH('NAZWA','DOKĄD'); -- przenosi gałąź NAZWA do kontenera DOKĄD
  2. CALL ADD_SIBLING('OBOK','NAZWA'); -- tworzy nowy węzeł "NAZWA" obok węzła OBOK
  3. CALL ADD_CHILD('RODZIC','NAZWA'); -- dodaje węzeł NAZWA do kontenera RODZIC
  4. CALL REMOVE_NODE('NAZWA'); -- usuwa węzeł NAZWA wraz z wszystkimi dziećmi
  5. CALL REMOVE_NODE_ONLY('NAZWA'); -- usuwa węzeł NAZWA, dzieci zostają przeniesione poziom wyżej

Templata z procedurami jest taka:
  1. DELIMITER $$
  2.  
  3. DROP PROCEDURE IF EXISTS `MOVE_BRANCH`$$
  4. CREATE PROCEDURE `MOVE_BRANCH` (IN `$FROM` VARCHAR(20), IN `$TO` VARCHAR(20))
  5. BEGIN
  6. START TRANSACTION;
  7.  
  8. /* cat_b.lft + 1 is the destination. */
  9. SELECT @destination := (lft + 1) FROM nested_category WHERE name = `$TO`;
  10.  
  11. SELECT @cat_a_width := ((rgt - lft) + 1) FROM nested_category WHERE name = `$FROM`;
  12.  
  13. /* Rip this table a new cat_a sized hole inside cat_b. */
  14. UPDATE nested_category SET rgt = rgt + @cat_a_width WHERE rgt >= @destination;
  15. UPDATE nested_category SET lft = lft + @cat_a_width WHERE lft >= @destination;
  16.  
  17. SELECT @cat_a_lft := lft, @cat_a_rgt := rgt FROM nested_category WHERE name = `$FROM`;
  18.  
  19. SELECT @diff := @destination - @cat_a_lft;
  20.  
  21. /* Move cat_a and all inhabitants to new hole */
  22. UPDATE nested_category SET rgt = rgt + @diff WHERE rgt BETWEEN @cat_a_lft AND @cat_a_rgt;
  23. UPDATE nested_category SET lft = lft + @diff WHERE lft BETWEEN @cat_a_lft AND @cat_a_rgt;
  24.  
  25. /* Close the gap created when we moved cat_a. */
  26. UPDATE nested_category SET rgt = rgt - @cat_a_width WHERE rgt >= @cat_a_lft;
  27. UPDATE nested_category SET lft = lft - @cat_a_width WHERE lft >= @cat_a_lft;
  28.  
  29. COMMIT;
  30. END$$
  31.  
  32. DROP PROCEDURE IF EXISTS `ADD_SIBLING`$$
  33. CREATE PROCEDURE `ADD_SIBLING`(IN `$container` VARCHAR(20), IN `$name` VARCHAR(20))
  34. BEGIN
  35. START TRANSACTION;
  36. SELECT @myRight := rgt FROM nested_category WHERE name = $container;
  37. UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myRight;
  38. UPDATE nested_category SET lft = lft + 2 WHERE lft > @myRight;
  39. INSERT INTO nested_category(name, lft, rgt) VALUES($name, @myRight + 1, @myRight + 2);
  40. COMMIT;
  41. END$$
  42.  
  43. DROP PROCEDURE IF EXISTS `ADD_CHILD`$$
  44. CREATE PROCEDURE `ADD_CHILD`(IN `$container` VARCHAR(20), IN `$name` VARCHAR(20))
  45. BEGIN
  46. START TRANSACTION;
  47. SELECT @myLeft := lft FROM nested_category WHERE name = $container;
  48. UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myLeft;
  49. UPDATE nested_category SET lft = lft + 2 WHERE lft > @myLeft;
  50. INSERT INTO nested_category(name, lft, rgt) VALUES($name, @myLeft + 1, @myLeft + 2);
  51. COMMIT;
  52. END$$
  53.  
  54. DROP PROCEDURE IF EXISTS `REMOVE_NODE`$$
  55. CREATE PROCEDURE `REMOVE_NODE`(IN `$name` VARCHAR(20))
  56. BEGIN
  57. START TRANSACTION;
  58. SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1 FROM nested_category WHERE name = $name;
  59. DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight;
  60. UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight;
  61. UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight;
  62. COMMIT;
  63. END$$
  64.  
  65. DROP PROCEDURE IF EXISTS `REMOVE_NODE_ONLY`$$
  66. CREATE PROCEDURE `REMOVE_NODE_ONLY`(IN `$name` VARCHAR(20))
  67. BEGIN
  68. START TRANSACTION;
  69. SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1 FROM nested_category WHERE name = $name;
  70. DELETE FROM nested_category WHERE lft = @myLeft;
  71. UPDATE nested_category SET rgt = rgt - 1, lft = lft - 1 WHERE lft BETWEEN @myLeft AND @myRight;
  72. UPDATE nested_category SET rgt = rgt - 2 WHERE rgt > @myRight;
  73. UPDATE nested_category SET lft = lft - 2 WHERE lft > @myRight;
  74. COMMIT;
  75. END$$
  76.  
  77. DELIMITER ;
wiewiorek
Dzięki everth smile.gif

Mi w nested set nie podoba się to, że trudno wskazać położenie nowo dodawanego potomka w stosunku do innych potomków - bo najwygodniej dodaje się potomka przed innymi potomkami albo po innych potomkach. Bo jak np. mamy coś takiego:
zwierzęta
/ | \
pies kot chomik

To w jaki sposób dać użytkownikowi możliwość dodania 'bocian' po 'kot' ? Trudno powiedzieć, najłatwiej po prostu zawsze dodawać nowy element w tym samym miejscu - czyli na końcu - po 'chomik' lub na początku - przed 'pies' lub zrobić dla użytkownika select listę z opcjami wyboru położenia nowo dodawanego elementu: 'na początku' i 'na końcu'. No bo jak inaczej ? Zrobić dla użytkownika select listę z opcjami dla nowo dodawanego elementu:
-umieść przed pies
-umieść po pies
-umieść przed kot
-umieść po kot
-umieść przed chomik
-umieść po chomik

to by było bez sensu. Dlatego wskazywanie położenia nowo dodawanego elementu w nested set jest utrudnione i praktycznie ograniczone do położenia przed lub po innych elementach na tym samym poziomie.


zegarek84
Cytat(wiewiorek @ 7.08.2010, 10:20:08 ) *
To w jaki sposób dać użytkownikowi możliwość dodania 'bocian' po 'kot' ? Trudno powiedzieć, najłatwiej po prostu zawsze dodawać nowy element w tym samym miejscu - czyli na końcu - po 'chomik' lub na początku - przed 'pies' lub zrobić dla użytkownika select listę z opcjami wyboru położenia nowo dodawanego elementu: 'na początku' i 'na końcu'. No bo jak inaczej ? Zrobić dla użytkownika select listę z opcjami dla nowo dodawanego elementu:
-umieść przed pies
-umieść po pies
-umieść przed kot
-umieść po kot
-umieść przed chomik
-umieść po chomik

to by było bez sensu. Dlatego wskazywanie położenia nowo dodawanego elementu w nested set jest utrudnione i praktycznie ograniczone do położenia przed lub po innych elementach na tym samym poziomie.

domyślnie to najlepiej dodawać na końcu (jak to tradycyjnie wszystko na końcu ląduje)... jednak jeśli chcesz taką listę robić i możliwość wyboru to zauważ, że powtórzyły się miejsca wyboru na tej liście... najnaturalniej by było jeśli chcesz listę rozwijalną to razem z grupowaniem gdzie wymieniasz elementy na tym samym poziomie a do wyboru jest tylko miejsce lub wymieniasz elementy normalnie nie w liście za koleją i miedzy tymi elementami masz input typu ratio domyślnie z zaznaczoną ostatnią opcją (czyli na końcu)...

poza tym w tabeli wg. mnie warto sobie to połączyć z parent_id (szybciej znajdziesz elementy na tym samym poziomie jeśli jest bardzo wiele poziomów...

samą wartość parent_id możesz sobie wygenerować np. jednym zapytaniem:
  1. SELECT node.*, parent.id AS parent FROM `test_tree` AS node, `test_tree` AS parent
  2. WHERE node.lft BETWEEN parent.lft AND parent.rgt AND node.id!=parent.id
  3. GROUP BY node.id
  4. ORDER BY parent.lft DESC

zaś wartości poprzypisywać w dodatkowej kolumnie np. tym:
  1. UPDATE `test_tree` SET p_id = (SELECT parent.id FROM `test_tree` AS node, `test_tree` AS parent
  2. WHERE `test_tree`.id = node.id AND node.lft BETWEEN parent.lft AND parent.rgt AND node.id!=parent.id
  3. GROUP BY node.id
  4. ORDER BY parent.lft DESC)

z kolei samo przenoszenie gałęzi już istniejących to jakoś wydaje mi się, że prościej skorzystać z tabel tymczasowych do tego celu przeznaczonych - ale jak kto woli...
ogólnie fajnie jest to wszystko opisane na tym blogu:
Me vs. tree czyli drzewka w MySQL i PHP
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.