Pour MySQL 8+: utilisez la with
syntaxe récursive .
Pour MySQL 5.x: utilisez des variables en ligne, des ID de chemin ou des auto-jointures.
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;
La valeur spécifiée dans parent_id = 19
doit être définie sur celle id
du parent dont vous souhaitez sélectionner tous les descendants.
MySQL 5.x
Pour les versions de MySQL qui ne prennent pas en charge les expressions de table communes (jusqu'à la version 5.7), vous obtiendriez cela avec la requête suivante:
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))
Voici un violon .
Ici, la valeur spécifiée dans @pv := '19'
doit être définie sur celle id
du parent dont vous souhaitez sélectionner tous les descendants.
Cela fonctionnera également si un parent a plusieurs enfants. Cependant, il est nécessaire que chaque enregistrement remplisse la condition parent_id < id
, sinon les résultats ne seront pas complets.
Affectations variables dans une requête
Cette requête utilise une syntaxe MySQL spécifique: les variables sont affectées et modifiées lors de son exécution. Certaines hypothèses sont émises sur l'ordre d'exécution:
- La
from
clause est évaluée en premier. C'est donc là que @pv
s'initialise.
- La
where
clause est évaluée pour chaque enregistrement dans l'ordre de récupération à partir des from
alias. C'est donc là qu'une condition est mise pour n'inclure que les enregistrements pour lesquels le parent a déjà été identifié comme étant dans l'arbre descendant (tous les descendants du parent principal sont progressivement ajoutés @pv
).
- Les conditions de cette
where
clause sont évaluées dans l'ordre et l'évaluation est interrompue une fois que le résultat global est certain. Par conséquent, la deuxième condition doit être à la deuxième place, car elle ajoute le id
à la liste parent, et cela ne doit se produire que si le id
passe la première condition. La length
fonction est uniquement appelée pour s'assurer que cette condition est toujours vraie, même si la pv
chaîne donnerait pour une raison quelconque une valeur fausse.
Dans l'ensemble, on peut trouver ces hypothèses trop risquées pour être fiables. La documentation met en garde:
vous obtiendrez peut-être les résultats escomptés, mais cela n'est pas garanti [...] l'ordre d'évaluation des expressions impliquant des variables utilisateur n'est pas défini.
Ainsi, même s'il fonctionne de manière cohérente avec la requête ci-dessus, l'ordre d'évaluation peut toujours changer, par exemple lorsque vous ajoutez des conditions ou utilisez cette requête comme vue ou sous-requête dans une requête plus grande. C'est une "fonctionnalité" qui sera supprimée dans une future version de MySQL :
Les versions précédentes de MySQL permettaient d'attribuer une valeur à une variable utilisateur dans des instructions autres que SET
. Cette fonctionnalité est prise en charge dans MySQL 8.0 pour des raisons de compatibilité descendante mais est susceptible d'être supprimée dans une future version de MySQL.
Comme indiqué ci-dessus, à partir de MySQL 8.0, vous devez utiliser la with
syntaxe récursive .
Efficacité
Pour les ensembles de données très volumineux, cette solution peut devenir lente, car l' find_in_set
opération n'est pas le moyen le plus idéal pour trouver un nombre dans une liste, certainement pas dans une liste qui atteint une taille dans le même ordre de grandeur que le nombre d'enregistrements renvoyés.
Alternative 1: with recursive
,connect by
De plus en plus de bases de données implémentent la syntaxe standard SQL: 1999 ISOWITH [RECURSIVE]
pour les requêtes récursives (par exemple 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+ ). Et à partir de la version 8.0, MySQL le prend également en charge . Voir le haut de cette réponse pour la syntaxe à utiliser.
Certaines bases de données ont une syntaxe alternative non standard pour les recherches hiérarchiques, comme la CONNECT BY
clause disponible sur Oracle , DB2 , Informix , CUBRID et d'autres bases de données.
MySQL version 5.7 n'offre pas une telle fonctionnalité. Lorsque votre moteur de base de données fournit cette syntaxe ou que vous pouvez migrer vers celle qui le fait, c'est certainement la meilleure option. Sinon, envisagez également les alternatives suivantes.
Alternative 2: identificateurs de style de chemin
Les choses deviennent beaucoup plus faciles si vous affectez des id
valeurs qui contiennent les informations hiérarchiques: un chemin. Par exemple, dans votre cas, cela pourrait ressembler à ceci:
ID | NAME
19 | category1
19/1 | category2
19/1/1 | category3
19/1/1/1 | category4
Alors votre select
ressemblerait à ceci:
select id,
name
from products
where id like '19/%'
Alternative 3: auto-jointures répétées
Si vous connaissez une limite supérieure pour la profondeur de votre arbre de hiérarchie, vous pouvez utiliser une sql
requête standard comme celle-ci:
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;
Voir ce violon
La where
condition spécifie le parent dont vous souhaitez récupérer les descendants. Vous pouvez étendre cette requête avec plus de niveaux si nécessaire.