Mysql
 sql >> Base de données >  >> RDS >> Mysql

Comment créer une requête récursive hiérarchique MySQL ?

Pour MySQL 8+ : utilisez le récursif with syntaxe.
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éfini sur 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 y parviendrez 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éfini sur 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 de variables dans une requête

Cette requête utilise une syntaxe MySQL spécifique :des variables sont affectées et modifiées lors de son exécution. Certaines hypothèses sont faites sur l'ordre d'exécution :

  • Le from clause est évaluée en premier. C'est donc là que @pv est initialisé.
  • Le where la clause est évaluée pour chaque enregistrement dans l'ordre d'extraction depuis le from pseudonymes. C'est donc ici qu'une condition est posée pour n'inclure que les enregistrements dont le parent a déjà été identifié comme étant dans l'arbre descendant (tous les descendants du parent primaire sont progressivement ajoutés à @pv ).
  • Les conditions dans ce where clause sont évaluées dans l'ordre, et l'évaluation est interrompue une fois que le résultat total est certain. Par conséquent, la deuxième condition doit être à la deuxième place, car elle ajoute le id à la liste des parents, et cela ne devrait se produire que si le id passe la première condition. La length la fonction est uniquement appelée pour s'assurer que cette condition est toujours vraie, même si le pv chaîne donnerait pour une raison quelconque une valeur fausse.

Dans l'ensemble, on peut trouver ces hypothèses trop risquées pour s'y fier. La documentation avertit :

vous pourriez obtenir les résultats que vous attendez, mais cela n'est pas garanti [...] l'ordre d'évaluation des expressions impliquant des variables utilisateur n'est pas défini.

Ainsi, même si cela 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 large. C'est une "fonctionnalité" qui sera supprimée à l'avenir Version 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 la rétrocompatibilité, 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 le with récursif syntaxe.

Efficacité

Pour les très grands ensembles de données, cette solution peut devenir lente, car le find_in_set 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 du 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 SQL:1999 ISO standard WITH [RECURSIVE] syntaxe 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 depuis version 8.0, MySQL le prend également en charge . Voir le début 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 le 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 celui qui le fait, c'est certainement la meilleure option à choisir. Si ce n'est pas le cas, envisagez également les alternatives suivantes.

Alternative 2 :Identifiants de style chemin

Les choses deviennent beaucoup plus faciles si vous attribuez id valeurs qui contiennent les informations hiérarchiques :un chemin. Par exemple, dans votre cas, cela pourrait ressembler à ceci :

identifiant NOM
19 category1
19/1 category2
19/1/1 category3
19/1/1/1 category4

Ensuite, votre select ressemblerait à ceci :

select  id,
        name 
from    products
where   id like '19/%'

Alternative 3 :Jointures répétitives

Si vous connaissez une limite supérieure pour la profondeur de votre arborescence hiérarchique, vous pouvez utiliser un sql standard requête 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

Le where condition spécifie de quel parent vous souhaitez récupérer les descendants. Vous pouvez étendre cette requête avec plus de niveaux si nécessaire.