Como obter todos os descendentes de um nó de árvore com consulta recursiva em MySql?
É realmente um problema para o MySql e é um ponto-chave para essa pergunta, mas você ainda tem algumas opções.
Supondo que você tenha esses dados de amostra, não tanto quanto sua amostra, mas o suficiente para demonstrar:
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);
Primeira escolha:código de nível
Como os dados de amostra da coluna name na tabela treeNode. (não sei dizer em inglês, comente-me sobre a expressão correta de
level code
.) Para obter todos os descendentes de
C1
ou G1
poderia ser simples assim:select * from treeNode where type = 'CATEGORY' and name like 'C1%' ;
select * from treeNode where type = 'GROUP' and name like 'G1%' ;
Eu prefiro muito essa abordagem, até preciso que geremos esses códigos antes de treeNode salvar em application. Será mais eficiente do que a consulta ou procedimento recursivo quando tivermos um grande número de registros. Eu acho que esta é uma boa abordagem de desnormalização.
Com essa abordagem, a declaração você deseja com join poderia ser:
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
Doce, não é?
Segunda escolha:número do nível
Anexe uma coluna de nível à tabela treeNode, conforme mostrado na DDL.
O número do nível é muito mais fácil de manter do que o código do nível em aplicação.
Com número de nível para obter todos os descendentes de
C1
ou G1
precisa de um pequeno truque como este: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) ;
Essa abordagem é mais lenta que a primeira, mas ainda mais rápida que a consulta recursiva.
O sql completo para a pergunta omitido. Só precisa substituir essas duas subconsultas de C e G por duas consultas acima.
Observação:
Existem muitas abordagens semelhantes, como aqui , aqui , ou mesmo aqui . Eles não funcionarão a menos que sejam ordenados por número de nível ou código de nível. Você pode testar a última consulta neste SqlFiddle alterando
order by level
para order by id
para ver as diferenças. Outra opção:o modelo de conjunto aninhado
Consulte este blog , ainda não testei. Mas eu acho que é semelhante às duas últimas opções.
É necessário adicionar um número à esquerda e um número à direita à tabela de nós de árvore para incluir os IDs de todos os descendentes entre eles.