Hvordan får man alle efterkommere fra en træknude med rekursiv forespørgsel i MySql?
Det er virkelig et problem for MySql, og det er et nøglepunkt i dette spørgsmål, men du har stadig nogle valgmuligheder.
Hvis du antager, at du har sådanne prøvedata, ikke så meget som din prøve, men nok til at demonstrere:
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);
Førstevalg:niveaukode
Ligesom eksempeldataene for navnekolonnen i treeNode-tabellen. (Jeg ved ikke, hvordan man siger det på engelsk, kommenter mig om det korrekte udtryk for level code
.)
For at få alle efterkommere af C1
eller G1
kunne være enkelt sådan her:
select * from treeNode where type = 'CATEGORY' and name like 'C1%' ;
select * from treeNode where type = 'GROUP' and name like 'G1%' ;
Jeg foretrækker denne tilgang meget, har endda brug for, at vi genererer denne kode, før treeNode gemmes i applikationen. Det vil være mere effektivt end rekursiv forespørgsel eller procedure, når vi har et stort antal poster. Jeg synes, det er en god denormaliseringstilgang.
Med denne tilgang, erklæringen du ønsker med join kunne være:
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ødt, ikke?
Andet valg:niveaunummer
Tilføj en niveaukolonne til treeNode-tabellen, som vist i DDL.
Niveaunummer er meget nemmere at vedligeholde end niveaukode i ansøgning.
Med niveaunummer for at få alle efterkommere af C1
eller G1
brug for et lille trick som dette:
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) ;
Denne tilgang er langsommere end den første, men stadig hurtigere end rekursiv forespørgsel.
Den fulde sql til spørgsmålet er udeladt. Behøver kun at erstatte de to underforespørgsler af C og G med to forespørgsler ovenfor.
Bemærk:
Der er mange lignende tilgange såsom her
, her
, eller endda her
. De virker ikke, medmindre de er bestilt efter niveaunummer eller niveaukode. Du kan teste den sidste forespørgsel i denne SqlFiddle
ved at ændre order by level
for at order by id
for at se forskellene.
Et andet valg:The Nested Set Model
Henvis venligst til denne blog , jeg har ikke testet endnu. Men jeg tror, det ligner sidste to valg.
Det skal du tilføje et venstre tal og et højre tal til trænode-tabellen for at omslutte alle efterkommeres id'er mellem dem.