Comment obtenir tous les descendants d'un nœud d'arbre avec une requête récursive dans MySql ?
C'est vraiment un problème pour MySql, et c'est un point clé de cette question, mais vous avez encore quelques choix.
En supposant que vous disposiez de tels exemples de données, pas autant que votre échantillon, mais suffisamment pour démontrer :
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);
Premier choix :code de niveau
Comme les exemples de données de la colonne de nom dans la table treeNode. (Je ne sais pas comment le dire en anglais, commentez-moi sur l'expression correcte de level code
.)
Pour obtenir tous les descendants de C1
ou G1
pourrait être simple comme ceci :
select * from treeNode where type = 'CATEGORY' and name like 'C1%' ;
select * from treeNode where type = 'GROUP' and name like 'G1%' ;
Je préfère beaucoup cette approche, j'ai même besoin que nous générions ces codes avant que treeNode ne soit enregistré dans l'application. Ce sera plus efficace qu'une requête ou une procédure récursive lorsque nous avons un grand nombre d'enregistrements. Je pense que c'est une bonne approche de dénormalisation.
Avec cette approche, la déclaration vous voulez avec jointure pourrait être :
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
Mignon, n'est-ce pas ?
Deuxième choix :numéro de niveau
Ajoutez une colonne de niveau à la table treeNode, comme indiqué dans le DDL.
Le numéro de niveau est beaucoup plus facile à gérer que le code de niveau en application.
Avec numéro de niveau pour obtenir tous les descendants de C1
ou G1
besoin d'une petite astuce comme celle-ci :
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) ;
Cette approche est plus lente que la première, mais toujours plus rapide que la requête récursive.
Le sql complet à la question omis. Il suffit de remplacer ces deux sous-requêtes de C et G par les deux requêtes ci-dessus.
Remarque :
Il existe de nombreuses approches similaires telles que ici
, ici
, ou même ici
. Ils ne fonctionneront que s'ils sont classés par numéro de niveau ou code de niveau. Vous pouvez tester la dernière requête dans ce SqlFiddle
en changeant order by level
pour order by id
pour voir les différences.
Un autre choix :le modèle d'ensemble imbriqué
Veuillez vous référer à ce blog , je n'ai pas encore testé. Mais je pense que c'est similaire aux deux derniers choix.
Il faut que vous ajoutiez un nombre gauche et un nombre droit à la table treenode pour enfermer tous les identifiants des descendants entre eux.