Jak utworzyć hierarchiczne zapytanie rekurencyjne MySQL


271

Mam tabelę MySQL, która jest następująca:

id | name        | parent_id
19 | category1   | 0
20 | category2   | 19
21 | category3   | 20
22 | category4   | 21
......

Teraz chcę mieć jedno zapytanie MySQL, do którego po prostu podaję identyfikator [na przykład powiedz „id = 19”], a następnie powinienem uzyskać wszystkie jego identyfikatory potomne [tzn. Wynik powinien mieć identyfikatory „20, 21,22”]. ... Hierarchia dzieci nie jest znana, może się różnić ...

Mam też rozwiązanie korzystające z pętli for ... Daj mi znać, jak to osiągnąć, używając pojedynczego zapytania MySQL, jeśli to możliwe.


Załóżmy, że hierarchia ma 7 poziomów. Jak powinna wyglądać tabela wyników?
Jonathan Leffler

1
MySQL (nadal) nie obsługuje zapytań hierarchicznych (podobnie jak inne nowoczesne DBMS). Musisz napisać procedurę składowaną lub użyć innego modelu danych.
a_horse_w_no_name


1
MYSQL 8.0 będzie obsługiwał zapytania rekurencyjne przy użyciu CTE (Common Table Expressions)
user3712320,

Co powiesz na uzyskanie pełnej listy postów zaczynając od identyfikatora ostatniego komentarza? Czy ostatnie dziecko?
Joe

Odpowiedzi:


392

W przypadku MySQL 8+: użyj withskładni rekurencyjnej .
W przypadku MySQL 5.x: użyj zmiennych wbudowanych, identyfikatorów ścieżek lub połączeń wewnętrznych.

MySQL 8+

with recursive cte (id, name, parent_id) as (
  select     id,
             name,
             parent_id
  from       products
  where      parent_id = 19
  union all
  select     p.id,
             p.name,
             p.parent_id
  from       products p
  inner join cte
          on p.parent_id = cte.id
)
select * from cte;

Podana wartość parent_id = 19powinna być ustawiona na idwartość rodzica, dla którego chcesz wybrać wszystkich potomków.

MySQL 5.x

W przypadku wersji MySQL, które nie obsługują typowych wyrażeń tabelowych (do wersji 5.7), można to osiągnąć za pomocą następującego zapytania:

select  id,
        name,
        parent_id 
from    (select * from products
         order by parent_id, id) products_sorted,
        (select @pv := '19') initialisation
where   find_in_set(parent_id, @pv)
and     length(@pv := concat(@pv, ',', id))

Oto skrzypce .

Tutaj wartość podana w @pv := '19'powinna być ustawiona na idwartość rodzica, dla którego chcesz wybrać wszystkich potomków.

Działa to również wtedy, gdy rodzic ma wiele dzieci. Wymagane jest jednak, aby każdy rekord spełniał warunek parent_id < id, w przeciwnym razie wyniki nie będą kompletne.

Zmienne przypisania w zapytaniu

To zapytanie używa określonej składni MySQL: zmienne są przypisywane i modyfikowane podczas jego wykonywania. Poczyniono pewne założenia dotyczące kolejności wykonania:

  • fromKlauzula jest oceniana jako pierwszy. To właśnie tam @pvzostaje zainicjowany.
  • whereKlauzula jest obliczane dla każdego rekordu w celu wydobycia od fromaliasów. Zatem w tym przypadku warunek obejmuje tylko rekordy, dla których rodzic został już zidentyfikowany jako znajdujący się w drzewie potomków (do potomków głównego rodzica dodawane są stopniowo @pv).
  • Warunki w tym wherepunkcie są oceniane w kolejności, a ocena jest przerywana, gdy całkowity wynik jest pewny. Dlatego drugi warunek musi znajdować się na drugim miejscu, ponieważ dodaje on iddo listy nadrzędnej, i powinno to się zdarzyć tylko wtedy, gdy idspełni pierwszy warunek. lengthFunkcja nazywa się tylko upewnić, warunek ten jest zawsze prawdziwe, nawet jeśli pvciąg będzie z jakiegoś powodu, uzyskując wartość falsy.

Podsumowując, można uznać, że założenia te są zbyt ryzykowne, aby na nich polegać. Dokumentacja ostrzega:

możesz uzyskać oczekiwane wyniki, ale nie ma gwarancji, [...] że kolejność obliczania wyrażeń zawierających zmienne użytkownika jest niezdefiniowana.

Nawet jeśli działa to spójnie z powyższym zapytaniem, kolejność oceny może się jeszcze zmienić, na przykład po dodaniu warunków lub użyciu tego zapytania jako widoku lub zapytania podrzędnego w większym zapytaniu. Jest to „funkcja”, która zostanie usunięta w przyszłej wersji MySQL :

Poprzednie wersje MySQL umożliwiały przypisywanie wartości zmiennej użytkownika w instrukcjach innych niż SET. Ta funkcja jest obsługiwana w MySQL 8.0 w celu zachowania zgodności wstecznej, ale może zostać usunięta w przyszłej wersji MySQL.

Jak wspomniano powyżej, od MySQL 8.0 należy używać withskładni rekurencyjnej .

Wydajność

W przypadku bardzo dużych zestawów danych to rozwiązanie może find_in_setdziałać wolniej, ponieważ operacja nie jest najbardziej idealnym sposobem znalezienia liczby na liście, a na pewno nie na liście, która osiąga rozmiar w tym samym rzędzie wielkości, co liczba zwracanych rekordów.

Alternatywa 1: with recursive,connect by

Coraz więcej baz danych implementuje standardową WITH [RECURSIVE]składnię SQL: 1999 ISO dla zapytań rekurencyjnych (np. Postgres 8.4+ , SQL Server 2005+ , DB2 , Oracle 11gR2 + , SQLite 3.8.4+ , Firebird 2.1+ , H2 , HyperSQL 2.1.0+ , Teradata , MariaDB 10.2.2+ ). Od wersji 8.0 MySQL obsługuje to . Zobacz na górę tej odpowiedzi, aby użyć składni.

Niektóre bazy danych mają alternatywną, niestandardową składnię do wyszukiwania hierarchicznego, na przykład CONNECT BYklauzula dostępna w Oracle , DB2 , Informix , CUBRID i innych bazach danych.

MySQL w wersji 5.7 nie oferuje takiej funkcji. Gdy aparat bazy danych udostępnia tę składnię lub możesz przeprowadzić migrację do takiej, która to robi, jest to z pewnością najlepsza opcja. Jeśli nie, rozważ także następujące alternatywy.

Alternatywa 2: Identyfikatory w stylu ścieżki

Sprawy stają się o wiele łatwiejsze, jeśli przypisujesz idwartości zawierające informacje hierarchiczne: ścieżkę. Na przykład w twoim przypadku może to wyglądać tak:

ID       | NAME
19       | category1   
19/1     | category2  
19/1/1   | category3  
19/1/1/1 | category4  

Wtedy twój selectwyglądałby tak:

select  id,
        name 
from    products
where   id like '19/%'

Alternatywa 3: Powtarzane samodzielne dołączanie

Jeśli znasz górną granicę głębokości, na jaką może stać się Twoje drzewo hierarchii, możesz użyć standardowego sqlzapytania takiego:

select      p6.parent_id as parent6_id,
            p5.parent_id as parent5_id,
            p4.parent_id as parent4_id,
            p3.parent_id as parent3_id,
            p2.parent_id as parent2_id,
            p1.parent_id as parent_id,
            p1.id as product_id,
            p1.name
from        products p1
left join   products p2 on p2.id = p1.parent_id 
left join   products p3 on p3.id = p2.parent_id 
left join   products p4 on p4.id = p3.parent_id  
left join   products p5 on p5.id = p4.parent_id  
left join   products p6 on p6.id = p5.parent_id
where       19 in (p1.parent_id, 
                   p2.parent_id, 
                   p3.parent_id, 
                   p4.parent_id, 
                   p5.parent_id, 
                   p6.parent_id) 
order       by 1, 2, 3, 4, 5, 6, 7;

Zobacz to skrzypce

Te whereokreśla, które rodzic stan chcesz pobrać potomków. W razie potrzeby możesz rozszerzyć to zapytanie o więcej poziomów.


26
Podoba mi się twoje wyjaśnienie. Nie tylko udziela odpowiedzi, ale wyjaśnia, dlaczego rozwiązuje problem, dzięki czemu możemy się z niego uczyć. EDYCJA: to świetnie, że nie polega ona na wcześniejszej znajomości liczby poziomów.
Byson

1
@Bison, bardzo doceniam twoje informacje zwrotne. Dzięki!
trincot

2
@ Avión, to nie jest coś, co musisz gdzieś umieścić, jest to wymóg , aby wszystkie zapisy spełniały ten warunek. Jeśli masz jeden lub więcej rekordów parent_id > id, nie możesz użyć tego rozwiązania.
trincot

2
Dla każdego, kto chce skorzystać z tej WITH RECURSIVEmetody, uważam, że następujący artykuł jest bardzo pomocny w przypadku różnych scenariuszy, takich jak głębokość rekurencji, rozróżnienia oraz cykle wykrywania i zamykania
Koń,

2
Próbowałem głównego rozwiązania na MySQL5.7 na moim komputerze, na własnych tabelach, ale to nie działało ze względu na odpowiednik klauzuli @pv: = concat (@pv, ',', id) zwraca wartość false. Naprawiłem to, zmieniając go na długość (@pv: = concat (@pv, ',', id))> 0, więc zawsze jest to prawda.
KC Wong

82

Z bloga Zarządzanie danymi hierarchicznymi w MySQL

Struktura tabeli

+-------------+----------------------+--------+
| category_id | name                 | parent |
+-------------+----------------------+--------+
|           1 | ELECTRONICS          |   NULL |
|           2 | TELEVISIONS          |      1 |
|           3 | TUBE                 |      2 |
|           4 | LCD                  |      2 |
|           5 | PLASMA               |      2 |
|           6 | PORTABLE ELECTRONICS |      1 |
|           7 | MP3 PLAYERS          |      6 |
|           8 | FLASH                |      7 |
|           9 | CD PLAYERS           |      6 |
|          10 | 2 WAY RADIOS         |      6 |
+-------------+----------------------+--------+

Pytanie:

SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS';

Wynik

+-------------+----------------------+--------------+-------+
| lev1        | lev2                 | lev3         | lev4  |
+-------------+----------------------+--------------+-------+
| ELECTRONICS | TELEVISIONS          | TUBE         | NULL  |
| ELECTRONICS | TELEVISIONS          | LCD          | NULL  |
| ELECTRONICS | TELEVISIONS          | PLASMA       | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS  | FLASH |
| ELECTRONICS | PORTABLE ELECTRONICS | CD PLAYERS   | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | 2 WAY RADIOS | NULL  |
+-------------+----------------------+--------------+-------+

Większość użytkowników w danym momencie miała do czynienia z danymi hierarchicznymi w bazie danych SQL i bez wątpienia dowiedziała się, że zarządzanie danymi hierarchicznymi nie jest tym, do czego przeznaczona jest relacyjna baza danych. Tabele relacyjnej bazy danych nie są hierarchiczne (jak XML), ale są po prostu płaską listą. Dane hierarchiczne mają relację rodzic-dziecko, która nie jest naturalnie reprezentowana w tabeli relacyjnej bazy danych. Czytaj więcej

Więcej szczegółów można znaleźć na blogu.

EDYTOWAĆ:

select @pv:=category_id as category_id, name, parent from category
join
(select @pv:=19)tmp
where parent=@pv

Wynik:

category_id name    parent
19  category1   0
20  category2   19
21  category3   20
22  category4   21

Odwołanie: Jak wykonać zapytanie rekurencyjne SELECT w Mysql?


23
W porządku, o ile w hierarchii nie ma więcej niż 4 poziomy. Jeśli istnieje N poziomów, musisz to wiedzieć, aby poprawnie utworzyć zapytanie.
Jonathan Leffler

2
@Damodaran, dziękuję za odpowiedź ... Potrzebowałem warunku, w którym liczba dzieci nie jest znana ... i na blogu, który korzysta z wewnętrznej koncepcji łączenia, ponieważ wymagana jest znajomość hierarchii, która nie jest znana w moim przypadku ... daj mi znać, co myślisz o tym samym ... Tak więc, w prostych słowach potrzebuję zapytania, aby obsłużyć poziomy zatrudnienia i zatrudnienia, w których 'n' nie jest znany .....
Tarun Parswani

1
@ user3036105: nie można tego zrobić w MySQL za pomocą pojedynczego zapytania SQL. MySQL po prostu nie jest do tego wystarczająco zaawansowany. Jeśli naprawdę tego potrzebujesz, rozważ uaktualnienie do DBMS, który obsługuje zapytania rekurencyjne.
a_horse_w_no_name

5
> Większość użytkowników w danym momencie miała do czynienia z danymi hierarchicznymi w bazie danych SQL i bez wątpienia dowiedziała się, że zarządzanie danymi hierarchicznymi nie jest tym, do czego przeznaczona jest relacyjna baza danych. Może miałeś na myśli bazę danych MySQL. Baza danych Oracle dość dobrze obsługuje dane hierarchiczne i zapytania.
Peter Nosko,

1
„... zarządzanie hierarchicznymi danymi nie jest tym, do czego przeznaczona jest relacyjna baza danych ...” Chociaż nie była to pierwotna intencja relacyjnej bazy danych, w rzeczywistości rzeczywiste dane hierarchiczne są niezwykle powszechne i MySQL powinien odzwierciedlać jak ludzie faktycznie muszą wykorzystywać swoje dane w rzeczywistych scenariuszach.
Dave L

9

Spróbuj tych:

Definicja tabeli:

DROP TABLE IF EXISTS category;
CREATE TABLE category (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(20),
    parent_id INT,
    CONSTRAINT fk_category_parent FOREIGN KEY (parent_id)
    REFERENCES category (id)
) engine=innodb;

Wiersze eksperymentalne:

INSERT INTO category VALUES
(19, 'category1', NULL),
(20, 'category2', 19),
(21, 'category3', 20),
(22, 'category4', 21),
(23, 'categoryA', 19),
(24, 'categoryB', 23),
(25, 'categoryC', 23),
(26, 'categoryD', 24);

Procedura przechowywana rekurencyjna:

DROP PROCEDURE IF EXISTS getpath;
DELIMITER $$
CREATE PROCEDURE getpath(IN cat_id INT, OUT path TEXT)
BEGIN
    DECLARE catname VARCHAR(20);
    DECLARE temppath TEXT;
    DECLARE tempparent INT;
    SET max_sp_recursion_depth = 255;
    SELECT name, parent_id FROM category WHERE id=cat_id INTO catname, tempparent;
    IF tempparent IS NULL
    THEN
        SET path = catname;
    ELSE
        CALL getpath(tempparent, temppath);
        SET path = CONCAT(temppath, '/', catname);
    END IF;
END$$
DELIMITER ;

Funkcja opakowania dla procedury składowanej:

DROP FUNCTION IF EXISTS getpath;
DELIMITER $$
CREATE FUNCTION getpath(cat_id INT) RETURNS TEXT DETERMINISTIC
BEGIN
    DECLARE res TEXT;
    CALL getpath(cat_id, res);
    RETURN res;
END$$
DELIMITER ;

Wybierz przykład:

SELECT id, name, getpath(id) AS path FROM category;

Wynik:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 19 | category1 | category1                               |
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
| 23 | categoryA | category1/categoryA                     |
| 24 | categoryB | category1/categoryA/categoryB           |
| 25 | categoryC | category1/categoryA/categoryC           |
| 26 | categoryD | category1/categoryA/categoryB/categoryD |
+----+-----------+-----------------------------------------+

Filtrowanie wierszy z określoną ścieżką:

SELECT id, name, getpath(id) AS path FROM category HAVING path LIKE 'category1/category2%';

Wynik:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
+----+-----------+-----------------------------------------+

1
To nie zadziała dla więcej niż jednego dziecka. np.(20, 'category2', 19), (21, 'category3', 20), (22, 'category4', 20),
Wyczyść kod

3
Jestem pewien, że to działa na więcej niż jedno dziecko. Nawet przetestowałem to ponownie.
Fandi Susanto

@Fandi Susanto, Dzięki, dzięki, pomaga mi.
Dogan Ozer

9

Najlepsze podejście, jakie wymyśliłem, to

  1. Użyj linii do przechowywania \ sortowania \ drzewek śledzenia. To więcej niż wystarcza i działa tysiące razy szybciej do czytania niż jakiekolwiek inne podejście. Pozwala również pozostać na tym wzorcu, nawet jeśli DB ulegnie zmianie (ponieważ DOWOLNY db pozwoli na użycie tego wzorca)
  2. Użyj funkcji, która określa rodowód dla określonego ID.
  3. Używaj go tak, jak chcesz (w selekcjach, operacjach CUD, a nawet w zadaniach).

Opis podejścia liniowego można znaleźć wszędzie, na przykład tutaj lub tutaj . Jako funkcji - to jest to, co mnie enspired.

W końcu - mniej lub bardziej proste, stosunkowo szybkie i PROSTE rozwiązanie.

Ciało funkcji

-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$

CREATE DEFINER=`root`@`localhost` FUNCTION `get_lineage`(the_id INT) RETURNS text CHARSET utf8
    READS SQL DATA
BEGIN

 DECLARE v_rec INT DEFAULT 0;

 DECLARE done INT DEFAULT FALSE;
 DECLARE v_res text DEFAULT '';
 DECLARE v_papa int;
 DECLARE v_papa_papa int DEFAULT -1;
 DECLARE csr CURSOR FOR 
  select _id,parent_id -- @n:=@n+1 as rownum,T1.* 
  from 
    (SELECT @r AS _id,
        (SELECT @r := table_parent_id FROM table WHERE table_id = _id) AS parent_id,
        @l := @l + 1 AS lvl
    FROM
        (SELECT @r := the_id, @l := 0,@n:=0) vars,
        table m
    WHERE @r <> 0
    ) T1
    where T1.parent_id is not null
 ORDER BY T1.lvl DESC;
 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    open csr;
    read_loop: LOOP
    fetch csr into v_papa,v_papa_papa;
        SET v_rec = v_rec+1;
        IF done THEN
            LEAVE read_loop;
        END IF;
        -- add first
        IF v_rec = 1 THEN
            SET v_res = v_papa_papa;
        END IF;
        SET v_res = CONCAT(v_res,'-',v_papa);
    END LOOP;
    close csr;
    return v_res;
END

A potem ty tylko

select get_lineage(the_id)

Mam nadzieję, że to komuś pomoże :)


9

Zrobił to samo w przypadku kolejnego zapytania

Mysql select rekurencyjny dostać wszystkie dziecko z wieloma poziomami

Zapytanie będzie:

SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM (
  SELECT @pv:=(
    SELECT GROUP_CONCAT(id SEPARATOR ',')
    FROM table WHERE parent_id IN (@pv)
  ) AS lv FROM table 
  JOIN
  (SELECT @pv:=1)tmp
  WHERE parent_id IN (@pv)
) a;

Jak możemy to zrobić? SELECT idFolder, (SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM ( SELECT @pv:=(SELECT GROUP_CONCAT(idFolder SEPARATOR ',') FROM Folder WHERE idFolderParent IN (@pv)) AS lv FROM Folder JOIN (SELECT @pv:= F1.idFolder )tmp WHERE idFolderParent IN (@pv)) a) from folder F1 where id > 10; Nie mogę polecić F1.idFolder dla @pv
Rahul

Odtworzyłem tabelę z pierwotnego pytania OP z danymi przedstawionymi w ich komentarzach, a następnie uruchomiłem zapytanie tutaj i otrzymałem jeden NULL. Czy wiesz, dlaczego tak może być? Czy istnieją wymagania wstępne dotyczące silnika bazy danych, czy zmieniło się coś od czasu udzielenia odpowiedzi, która powoduje, że to zapytanie jest nieaktualne?
Digital Ninja

7

Jeśli potrzebujesz dużej prędkości odczytu, najlepszą opcją jest użycie tabeli zamknięcia. Tabela zamknięcia zawiera wiersz dla każdej pary przodków / potomków. Tak więc w twoim przykładzie wyglądałaby tabela zamknięcia

ancestor | descendant | depth
0        | 0          | 0
0        | 19         | 1
0        | 20         | 2
0        | 21         | 3
0        | 22         | 4
19       | 19         | 0
19       | 20         | 1
19       | 21         | 3
19       | 22         | 4
20       | 20         | 0
20       | 21         | 1
20       | 22         | 2
21       | 21         | 0
21       | 22         | 1
22       | 22         | 0

Po utworzeniu tej tabeli zapytania hierarchiczne stają się bardzo łatwe i szybkie. Aby uzyskać wszystkich potomków kategorii 20:

SELECT cat.* FROM categories_closure AS cl
INNER JOIN categories AS cat ON cat.id = cl.descendant
WHERE cl.ancestor = 20 AND cl.depth > 0

Oczywiście, gdy używasz takich zdenormalizowanych danych, ma to duży minus. Musisz utrzymywać tabelę zamknięcia obok tabeli kategorii. Najlepszym sposobem jest prawdopodobnie użycie wyzwalaczy, ale nieco skomplikowane jest prawidłowe śledzenie wstawek / aktualizacji / usuwania tabel zamknięcia. Podobnie jak w przypadku innych elementów, musisz spojrzeć na swoje wymagania i zdecydować, jakie podejście jest dla Ciebie najlepsze.

Edycja : zobacz pytanie Jakie są opcje przechowywania danych hierarchicznych w relacyjnej bazie danych? po więcej opcji. Istnieją różne optymalne rozwiązania dla różnych sytuacji.


4

Proste zapytanie do listy dzieci pierwszego rekurencji:

select @pv:=id as id, name, parent_id
from products
join (select @pv:=19)tmp
where parent_id=@pv

Wynik:

id  name        parent_id
20  category2   19
21  category3   20
22  category4   21
26  category24  22

... z lewym złączeniem:

select
    @pv:=p1.id as id
  , p2.name as parent_name
  , p1.name name
  , p1.parent_id
from products p1
join (select @pv:=19)tmp
left join products p2 on p2.id=p1.parent_id -- optional join to get parent name
where p1.parent_id=@pv

Rozwiązanie @tincot, aby wyświetlić listę wszystkich dzieci:

select  id,
        name,
        parent_id 
from    (select * from products
         order by parent_id, id) products_sorted,
        (select @pv := '19') initialisation
where   find_in_set(parent_id, @pv) > 0
and     @pv := concat(@pv, ',', id)

Przetestuj online za pomocą Sql Fiddle i zobacz wszystkie wyniki.

http://sqlfiddle.com/#!9/a318e3/4/0


3

Możesz to zrobić w innych bazach danych dość łatwo za pomocą zapytania rekurencyjnego (YMMV na wydajności).

Innym sposobem na to jest przechowywanie dwóch dodatkowych bitów danych, wartości lewej i prawej. Lewa i prawa wartość są uzyskiwane z przejścia przez strukturę drzewa, którą reprezentujesz w przedsprzedaży.

Nazywa się to Zmodyfikowanym przejściem do drzewa zamówień przedpremierowych i pozwala uruchomić proste zapytanie, aby uzyskać wszystkie wartości nadrzędne naraz. Ma również nazwę „zestaw zagnieżdżony”.


Chciałem dodać podobny komentarz do twojego, ale skoro to zrobiłeś, dodam tylko link do dobrego przykładu „zestawu zagnieżdżonego”: mikehillyer.com/articles/managing-hierarchical-data-in-mysql
Mirosław Opoka

2

Wystarczy użyć klasy BlueM / tree php, aby utworzyć drzewo tabeli relacji własnych w mysql.

Drzewo i Drzewo \ Węzeł są klasami PHP do obsługi danych, które mają strukturę hierarchiczną z wykorzystaniem referencji nadrzędnego identyfikatora. Typowym przykładem jest tabela w relacyjnej bazie danych, w której „macierzyste” pole każdego rekordu odnosi się do klucza podstawowego innego rekordu. Oczywiście Tree nie może używać tylko danych pochodzących z bazy danych, ale cokolwiek: dostarczasz dane, a Tree korzysta z nich, niezależnie od tego, skąd dane pochodzą i jak zostały przetworzone. Czytaj więcej

Oto przykład użycia BlueM / tree:

<?php 
require '/path/to/vendor/autoload.php'; $db = new PDO(...); // Set up your database connection 
$stm = $db->query('SELECT id, parent, title FROM tablename ORDER BY title'); 
$records = $stm->fetchAll(PDO::FETCH_ASSOC); 
$tree = new BlueM\Tree($records); 
...

2

wprowadź opis zdjęcia tutaj

To tabela kategorii .

SELECT  id,
        NAME,
        parent_category 
FROM    (SELECT * FROM category
         ORDER BY parent_category, id) products_sorted,
        (SELECT @pv := '2') initialisation
WHERE   FIND_IN_SET(parent_category, @pv) > 0
AND     @pv := CONCAT(@pv, ',', id)

Wynik:: wprowadź opis zdjęcia tutaj


1
Czy możesz to wyjaśnić? Ale gwarantuję, że to działa. Dziękuję Ci.
wobsoriano

1
proszę wyjaśnić zapytanie i jakie jest znaczenie @pv? Jak działa pętla w tym zapytaniu?
Amanjot Kaur

2
Wydaje się, że nie działa na wszystkich poziomach, jeśli są dzieci, które mają niższe ID niż ich rodzice. :(
Jonas

1
@Jonas zajęło mi 20 minut, aby zidentyfikować rzeczywisty problem, próbując z inną kombinacją. tak, masz rację. Nie będzie działać z identyfikatorem niższym niż jego identyfikator nadrzędny. Czy masz jakieś rozwiązanie?
muaaz

@muaaz W końcu rozwiązałem go za pomocą pola „ścieżka”, które zawiera ścieżkę do odpowiedniego wiersza, np. wiersz o ID 577 ma ścieżkę „/ 1/2/45/577 /”. Jeśli szukasz wszystkich dzieci o ID 2, możesz po prostu wybrać wszystkie wiersze ze ścieżką LIKE „/ 1/2 /%”. Jedynym minusem jest to, że musisz zaktualizować ścieżki w metodach aktualizacji. Ale w przypadku MySQL 5.6 (kompatybilnego) było to jedyne rozwiązanie, które działało dla mnie.
Jonas,

1

To trochę trudne, sprawdź, czy to działa dla Ciebie

select a.id,if(a.parent = 0,@varw:=concat(a.id,','),@varw:=concat(a.id,',',@varw)) as list from (select * from recursivejoin order by if(parent=0,id,parent) asc) a left join recursivejoin b on (a.id = b.parent),(select @varw:='') as c  having list like '%19,%';

Link do skrzynki SQL http://www.sqlfiddle.com/#!2/e3cdf/2

Zastąp odpowiednio nazwą pola i tabelą.


W tym przypadku nie zadziała sqlfiddle.com/#!2/19360/2 , dzięki tej sztuczce przynajmniej powinieneś zamówić najpierw według poziomu hierarchicznego.
Jaugar Chang

1

Coś tu nie wymienionego, choć nieco podobne do drugiej alternatywy zaakceptowanej odpowiedzi, ale inne i niskie koszty dla zapytania o dużej hierarchii i łatwych (wstawianie aktualizacji usuń) elementów, dodałoby stałą kolumnę ścieżki dla każdego elementu.

niektóre jak:

id | name        | path
19 | category1   | /19
20 | category2   | /19/20
21 | category3   | /19/20/21
22 | category4   | /19/20/21/22

Przykład:

-- get children of category3:
SELECT * FROM my_table WHERE path LIKE '/19/20/21%'
-- Reparent an item:
UPDATE my_table SET path = REPLACE(path, '/19/20', '/15/16') WHERE path LIKE '/19/20/%'

Zoptymalizuj długość ścieżki i ORDER BY pathużywając kodowania base36 zamiast prawdziwego identyfikatora ścieżki numerycznej

 // base10 => base36
 '1' => '1',
 '10' => 'A',
 '100' => '2S',
 '1000' => 'RS',
 '10000' => '7PS',
 '100000' => '255S',
 '1000000' => 'LFLS',
 '1000000000' => 'GJDGXS',
 '1000000000000' => 'CRE66I9S'

https://en.wikipedia.org/wiki/Base36

Pomijanie również separatora ukośnika „/” za pomocą stałej długości i dopełniania do zakodowanego identyfikatora

Szczegółowe wyjaśnienie dotyczące optymalizacji tutaj: https://bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-materialized-path/

DO ZROBIENIA

budowanie funkcji lub procedury podziału ścieżki dla wycofujących się przodków jednego elementu


Dzięki! Ciekawe zbase36
Vlad

0

To działa dla mnie, mam nadzieję, że to również zadziała dla ciebie. Daje ci zestaw rekordów Root to Child dla dowolnego konkretnego menu. Zmień nazwę pola zgodnie ze swoimi wymaganiami.

SET @id:= '22';

SELECT Menu_Name, (@id:=Sub_Menu_ID ) as Sub_Menu_ID, Menu_ID 
FROM 
    ( SELECT Menu_ID, Menu_Name, Sub_Menu_ID 
      FROM menu 
      ORDER BY Sub_Menu_ID DESC
    ) AS aux_table 
    WHERE Menu_ID = @id
     ORDER BY Sub_Menu_ID;

Wydaje się, że nie działa na wszystkich poziomach, jeśli są dzieci, które mają większe ID niż ich rodzice
muaaz

-1

Łatwiej mi było:

1) utwórz funkcję, która sprawdzi, czy element znajduje się w hierarchii nadrzędnej innego elementu. Coś w tym stylu (nie napiszę funkcji, zrób to przy WHILE DO):

is_related(id, parent_id);

w twoim przykładzie

is_related(21, 19) == 1;
is_related(20, 19) == 1;
is_related(21, 18) == 0;

2) użyj podselekcji, coś takiego:

select ...
from table t
join table pt on pt.id in (select i.id from table i where is_related(t.id,i.id));

-1

Zrobiłem dla ciebie zapytanie. To da Ci kategorię rekurencyjną z jednym zapytaniem:

SELECT id,NAME,'' AS subName,'' AS subsubName,'' AS subsubsubName FROM Table1 WHERE prent is NULL
UNION 
SELECT b.id,a.name,b.name AS subName,'' AS subsubName,'' AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id WHERE a.prent is NULL AND b.name IS NOT NULL 
UNION 
SELECT c.id,a.name,b.name AS subName,c.name AS subsubName,'' AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id LEFT JOIN Table1 AS c ON c.prent=b.id WHERE a.prent is NULL AND c.name IS NOT NULL 
UNION 
SELECT d.id,a.name,b.name AS subName,c.name AS subsubName,d.name AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id LEFT JOIN Table1 AS c ON c.prent=b.id LEFT JOIN Table1 AS d ON d.prent=c.id WHERE a.prent is NULL AND d.name IS NOT NULL 
ORDER BY NAME,subName,subsubName,subsubsubName

Oto skrzypce .


Usuń / edytuj swoją odpowiedź, aby odzyskać pozytywną reputację.
fWd82
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.