Per MySQL 8+: utilizzare la with
sintassi ricorsiva .
Per MySQL 5.x: utilizzare variabili in linea, ID percorso o self-join.
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;
Il valore specificato in parent_id = 19
deve essere impostato id
su del padre di cui si desidera selezionare tutti i discendenti.
MySQL 5.x
Per le versioni di MySQL che non supportano le espressioni di tabella comuni (fino alla versione 5.7), si otterrebbe questo risultato con la seguente query:
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))
Ecco un violino .
Qui, il valore specificato in @pv := '19'
dovrebbe essere impostato id
su del padre di cui si desidera selezionare tutti i discendenti.
Questo funzionerà anche se un genitore ha più figli. Tuttavia, è necessario che ogni record soddisfi la condizione parent_id < id
, altrimenti i risultati non saranno completi.
Assegnazioni di variabili all'interno di una query
Questa query utilizza una sintassi MySQL specifica: le variabili vengono assegnate e modificate durante la sua esecuzione. Vengono fatte alcune ipotesi sull'ordine di esecuzione:
- La
from
clausola viene valutata per prima. È qui che @pv
viene inizializzato.
- La
where
clausola viene valutata per ciascun record nell'ordine di recupero dagli from
alias. Quindi è qui che viene inserita una condizione per includere solo i record per i quali il genitore è già stato identificato come albero dell'albero discendente (tutti i discendenti del genitore primario vengono progressivamente aggiunti @pv
).
- Le condizioni in questa
where
clausola sono valutate in ordine e la valutazione viene interrotta una volta che il risultato totale è certo. Pertanto, la seconda condizione deve trovarsi al secondo posto, in quanto aggiunge l' id
elenco padre e ciò dovrebbe verificarsi solo se id
passa la prima condizione. La length
funzione viene chiamata solo per assicurarsi che questa condizione sia sempre vera, anche se pv
per qualche motivo la stringa restituisse un valore errato.
Tutto sommato, si potrebbero trovare queste ipotesi troppo rischiose per poter contare. La documentazione avverte:
potresti ottenere i risultati che ti aspetti, ma ciò non è garantito [...] l'ordine di valutazione per le espressioni che coinvolgono le variabili utente non è definito.
Pertanto, anche se funziona in modo coerente con la query precedente, l'ordine di valutazione può comunque cambiare, ad esempio quando si aggiungono condizioni o si utilizza questa query come vista o query secondaria in una query più grande. È una "caratteristica" che verrà rimossa in una futura versione di MySQL :
Nelle versioni precedenti di MySQL era possibile assegnare un valore a una variabile utente in istruzioni diverse da SET
. Questa funzionalità è supportata in MySQL 8.0 per la compatibilità con le versioni precedenti ma è soggetta a rimozione in una versione futura di MySQL.
Come detto sopra, da MySQL 8.0 in poi dovresti usare la with
sintassi ricorsiva .
Efficienza
Per set di dati molto grandi questa soluzione potrebbe rallentare, poiché l' find_in_set
operazione non è il modo più ideale per trovare un numero in un elenco, certamente non in un elenco che raggiunge una dimensione nello stesso ordine di grandezza del numero di record restituiti.
Alternativa 1: with recursive
,connect by
Sempre più database implementano la sintassi standard SQL: 1999 ISOWITH [RECURSIVE]
per le query ricorsive (ad esempio 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 partire dalla versione 8.0, anche MySQL lo supporta . Vedere la parte superiore di questa risposta per la sintassi da utilizzare.
Alcuni database hanno una sintassi alternativa e non standard per le ricerche gerarchiche, come la CONNECT BY
clausola disponibile su Oracle , DB2 , Informix , CUBRID e altri database.
MySQL versione 5.7 non offre tale funzionalità. Quando il tuo motore di database fornisce questa sintassi o puoi migrare a uno che lo fa, questa è sicuramente l'opzione migliore da scegliere. In caso contrario, considera anche le seguenti alternative.
Alternativa 2: identificatori stile percorso
Le cose diventano molto più semplici se si assegnassero id
valori che contengono le informazioni gerarchiche: un percorso. Ad esempio, nel tuo caso questo potrebbe apparire così:
ID | NAME
19 | category1
19/1 | category2
19/1/1 | category3
19/1/1/1 | category4
Quindi il tuo select
sarebbe simile a questo:
select id,
name
from products
where id like '19/%'
Alternativa 3: ripetuti self-join
Se conosci un limite massimo per quanto può essere profondo il tuo albero gerarchico, puoi utilizzare una sql
query standard come questa:
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;
Vedi questo violino
La where
condizione specifica di quale genitore desideri recuperare i discendenti. È possibile estendere questa query con più livelli in base alle esigenze.