Para o MySQL 8+: use a with
sintaxe recursiva .
Para o MySQL 5.x: use variáveis embutidas, IDs de caminho ou associações automáticas.
MySQL 8+
with recursive cte (id, name, parent_id) as (
select id,
name,
parent_id
from products
where parent_id = 19
union all
select p.id,
p.name,
p.parent_id
from products p
inner join cte
on p.parent_id = cte.id
)
select * from cte;
O valor especificado em parent_id = 19
deve ser definido como id
o pai, do qual você deseja selecionar todos os descendentes.
MySQL 5.x
Para versões do MySQL que não suportam expressões de tabela comuns (até a versão 5.7), você faria isso com a seguinte consulta:
select id,
name,
parent_id
from (select * from products
order by parent_id, id) products_sorted,
(select @pv := '19') initialisation
where find_in_set(parent_id, @pv)
and length(@pv := concat(@pv, ',', id))
Aqui está um violino .
Aqui, o valor especificado em @pv := '19'
deve ser definido como o id
do pai, do qual você deseja selecionar todos os descendentes.
Isso funcionará também se um pai tiver vários filhos. No entanto, é necessário que cada registro atenda à condição parent_id < id
, caso contrário, os resultados não serão completos.
Atribuições variáveis dentro de uma consulta
Esta consulta usa sintaxe específica do MySQL: variáveis são atribuídas e modificadas durante sua execução. Algumas suposições são feitas sobre a ordem de execução:
- A
from
cláusula é avaliada primeiro. Então é aí que é @pv
inicializado.
- A
where
cláusula é avaliada para cada registro na ordem de recuperação dos from
aliases. Portanto, é aqui que uma condição é colocada para incluir apenas registros para os quais o pai já foi identificado como estando na árvore descendente (todos os descendentes do pai primário são adicionados progressivamente @pv
).
- As condições nesta
where
cláusula são avaliadas em ordem e a avaliação é interrompida quando o resultado total é certo. Portanto, a segunda condição deve estar em segundo lugar, pois ela adiciona id
à lista pai, e isso só deve acontecer se id
passar na primeira condição. A length
função é chamada apenas para garantir que essa condição seja sempre verdadeira, mesmo se a pv
string, por algum motivo, produzir um valor falso.
Em suma, pode-se achar essas suposições arriscadas demais para se confiar. A documentação alerta:
você pode obter os resultados esperados, mas isso não é garantido [...] a ordem de avaliação para expressões que envolvem variáveis de usuário é indefinida.
Portanto, mesmo que funcione de maneira consistente com a consulta acima, a ordem de avaliação ainda pode ser alterada, por exemplo, quando você adiciona condições ou usa essa consulta como uma exibição ou subconsulta em uma consulta maior. É um "recurso" que será removido em uma versão futura do MySQL :
Versões anteriores do MySQL tornaram possível atribuir um valor a uma variável de usuário em declarações diferentes de SET
. Esta funcionalidade é suportada no MySQL 8.0 para compatibilidade com versões anteriores, mas está sujeita a remoção em uma versão futura do MySQL.
Como mencionado acima, do MySQL 8.0 em diante, você deve usar a with
sintaxe recursiva .
Eficiência
Para conjuntos de dados muito grandes, essa solução pode ficar lenta, pois a find_in_set
operação não é a maneira mais ideal de encontrar um número em uma lista, certamente não em uma lista que atinja um tamanho na mesma ordem de magnitude que o número de registros retornados.
Alternativa 1: with recursive
,connect by
Mais e mais bancos de dados implementam a sintaxe padrão SQL: 1999 ISOWITH [RECURSIVE]
para consultas recursivas (por exemplo, Postgres 8.4+ , SQL Server 2005+ , DB2 , Oracle 11gR2 + , SQLite 3.8.4+ , Firebird 2.1+ , H2 , HyperSQL 2.1.0+ , Teradata , MariaDB 10.2.2+ ). E a partir da versão 8.0, também o MySQL suporta . Veja o topo desta resposta para a sintaxe a ser usada.
Alguns bancos de dados possuem uma sintaxe alternativa não padrão para pesquisas hierárquicas, como a CONNECT BY
cláusula disponível no Oracle , DB2 , Informix , CUBRID e outros bancos de dados.
O MySQL versão 5.7 não oferece esse recurso. Quando o mecanismo de banco de dados fornece essa sintaxe ou você pode migrar para um que sim, essa é certamente a melhor opção. Caso contrário, considere também as seguintes alternativas.
Alternativa 2: identificadores no estilo de caminho
As coisas se tornam muito mais fáceis se você atribuir id
valores que contenham informações hierárquicas: um caminho. Por exemplo, no seu caso, isso pode ser assim:
ID | NAME
19 | category1
19/1 | category2
19/1/1 | category3
19/1/1/1 | category4
Então você select
ficaria assim:
select id,
name
from products
where id like '19/%'
Alternativa 3: auto-junções repetidas
Se você conhece um limite superior para a profundidade da sua árvore hierárquica, pode usar uma sql
consulta padrão como esta:
select p6.parent_id as parent6_id,
p5.parent_id as parent5_id,
p4.parent_id as parent4_id,
p3.parent_id as parent3_id,
p2.parent_id as parent2_id,
p1.parent_id as parent_id,
p1.id as product_id,
p1.name
from products p1
left join products p2 on p2.id = p1.parent_id
left join products p3 on p3.id = p2.parent_id
left join products p4 on p4.id = p3.parent_id
left join products p5 on p5.id = p4.parent_id
left join products p6 on p6.id = p5.parent_id
where 19 in (p1.parent_id,
p2.parent_id,
p3.parent_id,
p4.parent_id,
p5.parent_id,
p6.parent_id)
order by 1, 2, 3, 4, 5, 6, 7;
Veja este violino
A where
condição especifica de qual pai você deseja recuperar os descendentes. Você pode estender essa consulta com mais níveis, conforme necessário.