Jak pobrać wszystkich potomków z węzła drzewa za pomocą zapytania rekurencyjnego w MySql?
To naprawdę problem dla MySql i jest to kluczowy punkt tego pytania, ale nadal masz kilka możliwości.
Zakładając, że masz takie przykładowe dane, nie tyle, co próbka, ale wystarczająco dużo, aby zademonstrować:
create table treeNode(
id int, parent_id int, name varchar(10), type varchar(10),level int);
insert into treeNode
(id, parent_id, name, type, level) values
( 1, 0, 'C1 ', 'CATEGORY', 1),
( 2, 1, 'C1.1 ', 'CATEGORY', 2),
( 3, 2, 'C1.1.1', 'CATEGORY', 3),
( 4, 1, 'C1.2 ', 'CATEGORY', 2),
( 5, 4, 'C1.2.1', 'CATEGORY', 3),
( 3, 8, 'G1.1.1', 'GROUP', 3),
( 4, 9, 'G1.2 ', 'GROUP', 2),
( 5, 4, 'G1.2.1', 'GROUP', 3),
( 8, 9, 'G1.1 ', 'GROUP', 2),
( 9, 0, 'G1 ', 'GROUP', 1);
Pierwszy wybór:kod poziomu
Podobnie jak przykładowe dane kolumny nazwy w tabeli treeNode. (Nie wiem, jak to powiedzieć po angielsku, skomentuj mi poprawne wyrażenie level code
.)
Aby pobrać wszystkich potomków C1
lub G1
może być tak proste:
select * from treeNode where type = 'CATEGORY' and name like 'C1%' ;
select * from treeNode where type = 'GROUP' and name like 'G1%' ;
Bardzo wolę to podejście, nawet musimy wygenerować ten kod przed zapisaniem treeNode w aplikacji. Będzie bardziej wydajne niż rekurencyjne zapytanie lub procedura, gdy mamy dużą liczbę rekordów. Myślę, że to dobre podejście do denormalizacji.
Przy takim podejściu oświadczenie chcesz z dołączeniem może być:
SELECT distinct p.* --if there is only one tree node for a product, distinct is not needed
FROM product p
JOIN product_type pt
ON pt.id= p.parent_id -- to get product type of a product
JOIN linked_TreeNode LC
ON LC.product_id= p.id -- to get tree_nodes related to a product
JOIN (select * from treeNode where type = 'CATEGORY' and name like 'C1%' ) C --may replace C1% to concat('$selected_cat_name','%')
ON LC.treeNode_id = C.id
JOIN (select * from treeNode where type = 'GROUP' and name like 'G1%' ) G --may replace G1% to concat('$selected_group_name','%')
ON LC.treeNode_id = G.id
WHERE pt.name = '$selected_type' -- filter selected product type, assuming using product.name, if using product.parent_id, can save one join by pt like your original sql
Słodko, prawda?
Drugi wybór:numer poziomu
Dołącz kolumnę poziomu do tabeli treeNode, jak pokazano w DDL.
Numer poziomu jest znacznie łatwiejszy w utrzymaniu niż kod poziomu w aplikacji.
Z numerem poziomu, aby uzyskać wszystkich potomków C1
lub G1
potrzebujesz takiej sztuczki:
SELECT id, parent_id, name, type, @pv:=concat(@pv,',',id) as link_ids
FROM (select * from treeNode where type = 'CATEGORY' order by level) as t
JOIN (select @pv:='1')tmp
WHERE find_in_set(parent_id,@pv)
OR find_in_set(id,@pv);
-- get all descendants of `C1`
SELECT id, parent_id, name, type, @pv:=concat(@pv,',',id) as link_ids
FROM (select * from treeNode where type = 'GROUP' order by level) as t
JOIN (select @pv:=',9,')tmp
WHERE find_in_set(parent_id,@pv)
OR find_in_set(id,@pv) ;
To podejście jest wolniejsze niż pierwsze, ale wciąż szybsze niż zapytanie rekurencyjne.
Pominięto pełny sql do pytania. Wystarczy zastąpić te dwa podzapytania C i G dwoma powyższymi zapytaniami.
Uwaga:
Istnieje wiele podobnych podejść, takich jak tutaj
, tutaj
, a nawet tutaj
. Nie będą działać, jeśli nie zostaną uporządkowane według numeru poziomu lub kodu poziomu. Możesz przetestować ostatnie zapytanie w tym SqlFiddle
zmieniając order by level
do order by id
aby zobaczyć różnice.
Inny wybór:model zestawu zagnieżdżonego
Proszę odnieść się do tego bloga , jeszcze nie testowałem. Ale myślę, że jest podobny do dwóch ostatnich wyborów.
Musisz dodać lewą i prawą liczbę do tabeli treenode, aby zawrzeć między nimi wszystkie identyfikatory potomków.