Wie bekomme ich alle Nachkommen von einem Baumknoten mit rekursiver Abfrage in MySql?
Es ist wirklich ein Problem für MySql, und es ist ein Schlüsselpunkt für diese Frage, aber Sie haben immer noch einige Möglichkeiten.
Angenommen, Sie haben solche Beispieldaten, nicht so viele wie Ihr Beispiel, aber genug, um Folgendes zu demonstrieren:
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);
Erste Wahl:Stufencode
Wie die Beispieldaten der Namensspalte in der Tabelle treeNode. (Ich weiß nicht, wie ich es auf Englisch sagen soll, kommentieren Sie mich zum korrekten Ausdruck von level code
.)
Um alle Nachkommen von C1
zu erhalten oder G1
könnte so einfach sein:
select * from treeNode where type = 'CATEGORY' and name like 'C1%' ;
select * from treeNode where type = 'GROUP' and name like 'G1%' ;
Ich bevorzuge diesen Ansatz sehr, muss sogar diesen Code generieren, bevor treeNode in der Anwendung gespeichert wird. Es ist effizienter als eine rekursive Abfrage oder Prozedur, wenn wir eine große Anzahl von Datensätzen haben. Ich denke, das ist ein guter Denormalisierungsansatz.
Bei diesem Ansatz ist die Aussage Sie möchten mit beitreten könnte sein:
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üß, nicht wahr?
Zweite Wahl:Levelnummer
Hängen Sie eine Ebenenspalte an die treeNode-Tabelle an, wie in der DDL gezeigt.
Die Levelnummer ist viel einfacher zu verwalten als der Levelcode in der Anwendung.
Mit Levelnummer, um alle Nachkommen von C1
zu erhalten oder G1
brauche einen kleinen Trick wie diesen:
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) ;
Dieser Ansatz ist langsamer als der erste, aber immer noch schneller als die rekursive Abfrage.
Die vollständige SQL zur Frage wurde weggelassen. Es müssen nur diese beiden Unterabfragen von C und G durch zwei Abfragen oben ersetzt werden.
Hinweis:
Es gibt viele ähnliche Ansätze wie hier
, hier
, oder sogar hier
. Sie funktionieren nur, wenn sie nach Levelnummer oder Levelcode bestellt werden. Sie könnten die letzte Abfrage in diesem SqlFiddle
testen indem Sie order by level
ändern um order by id
um die Unterschiede zu sehen.
Eine weitere Wahl:Das Nested-Set-Modell
Bitte beziehen Sie sich auf diesen Blog , habe ich noch nicht getestet. Aber ich denke, es ist ähnlich wie bei den letzten beiden Optionen.
Sie müssen der Treenode-Tabelle eine linke und eine rechte Zahl hinzufügen, um die IDs aller Nachkommen zwischen sich einzuschließen.