Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

> pytanie o nested set
wiewiorek
post
Post #1





Grupa: Zarejestrowani
Postów: 247
Pomógł: 11
Dołączył: 5.09.2009

Ostrzeżenie: (0%)
-----


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.


Go to the top of the page
+Quote Post
 
Start new topic
Odpowiedzi (1 - 11)
Zyx
post
Post #2





Grupa: Zarejestrowani
Postów: 952
Pomógł: 154
Dołączył: 20.01.2007
Skąd: /dev/oracle

Ostrzeżenie: (0%)
-----


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`
Go to the top of the page
+Quote Post
wiewiorek
post
Post #3





Grupa: Zarejestrowani
Postów: 247
Pomógł: 11
Dołączył: 5.09.2009

Ostrzeżenie: (0%)
-----


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 ?
Go to the top of the page
+Quote Post
Crozin
post
Post #4





Grupa: Zarejestrowani
Postów: 6 476
Pomógł: 1306
Dołączył: 6.08.2006
Skąd: Kraków

Ostrzeżenie: (0%)
-----


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.
Go to the top of the page
+Quote Post
wiewiorek
post
Post #5





Grupa: Zarejestrowani
Postów: 247
Pomógł: 11
Dołączył: 5.09.2009

Ostrzeżenie: (0%)
-----


Tylko takie przenoszenie gałęzi oznacza wielokrotny update co moze prowadzic do anomalii.

Go to the top of the page
+Quote Post
everth
post
Post #6





Grupa: Zarejestrowani
Postów: 782
Pomógł: 153
Dołączył: 21.07.2010

Ostrzeżenie: (0%)
-----


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 (IMG:style_emoticons/default/smile.gif)
Go to the top of the page
+Quote Post
erix
post
Post #7





Grupa: Moderatorzy
Postów: 15 467
Pomógł: 1451
Dołączył: 25.04.2005
Skąd: Szczebrzeszyn/Rzeszów




Cytat
gałęzi oznacza wielokrotny update co moze prowadzic do anomalii.

Blokowanie tabeli albo transakcja.
Go to the top of the page
+Quote Post
Crozin
post
Post #8





Grupa: Zarejestrowani
Postów: 6 476
Pomógł: 1306
Dołączył: 6.08.2006
Skąd: Kraków

Ostrzeżenie: (0%)
-----


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.
Go to the top of the page
+Quote Post
wiewiorek
post
Post #9





Grupa: Zarejestrowani
Postów: 247
Pomógł: 11
Dołączył: 5.09.2009

Ostrzeżenie: (0%)
-----


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;


Ten post edytował wiewiorek 5.08.2010, 20:13:44
Go to the top of the page
+Quote Post
everth
post
Post #10





Grupa: Zarejestrowani
Postów: 782
Pomógł: 153
Dołączył: 21.07.2010

Ostrzeżenie: (0%)
-----


@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 (IMG:style_emoticons/default/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 ;


Ten post edytował everth 7.08.2010, 00:08:54
Go to the top of the page
+Quote Post
wiewiorek
post
Post #11





Grupa: Zarejestrowani
Postów: 247
Pomógł: 11
Dołączył: 5.09.2009

Ostrzeżenie: (0%)
-----


Dzięki everth (IMG:style_emoticons/default/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.


Go to the top of the page
+Quote Post
zegarek84
post
Post #12





Grupa: Zarejestrowani
Postów: 1 332
Pomógł: 294
Dołączył: 12.10.2008
Skąd: Olkusz

Ostrzeżenie: (0%)
-----


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

Ten post edytował zegarek84 7.08.2010, 11:37:14
Go to the top of the page
+Quote Post

Reply to this topicStart new topic
2 Użytkowników czyta ten temat (2 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Aktualny czas: 23.08.2025 - 16:01