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

272

J'ai une table MySQL qui se présente comme suit:

id | name        | parent_id
19 | category1   | 0
20 | category2   | 19
21 | category3   | 20
22 | category4   | 21
......

Maintenant, je veux avoir une seule requête MySQL à laquelle je fournis simplement l'identifiant [par exemple, dire 'id = 19'] alors je devrais obtenir tous ses identifiants enfants [c'est-à-dire que le résultat devrait avoir les identifiants '20, 21,22 ']. ... Aussi, la hiérarchie des enfants n'est pas connue elle peut varier ....

De plus, j'ai déjà la solution en utilisant la boucle for ..... Faites-moi savoir comment obtenir la même chose en utilisant une seule requête MySQL si possible.

Tarun Parswani
la source
Supposons que la hiérarchie soit profonde de 7 niveaux. À quoi pensez-vous que la table de sortie ressemblera?
Jonathan Leffler
1
MySQL (toujours) ne prend pas en charge les requêtes hiérarchiques (comme le font d'autres SGBD modernes). Vous devrez écrire une procédure stockée ou utiliser un modèle de données différent.
a_horse_with_no_name
1
MYSQL 8.0 prendra en charge la requête récursive à l'aide de CTE (Common Table Expressions)
user3712320
Qu'en est-il d'obtenir la liste complète des messages à partir du dernier identifiant de commentaire? Ou le dernier enfant?
joe

Réponses:

394

Pour MySQL 8+: utilisez la withsyntaxe 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 = 19doit être définie sur celle iddu 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 iddu 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 fromclause est évaluée en premier. C'est donc là que @pvs'initialise.
  • La whereclause est évaluée pour chaque enregistrement dans l'ordre de récupération à partir des fromalias. 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 whereclause 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 idpasse la première condition. La lengthfonction est uniquement appelée pour s'assurer que cette condition est toujours vraie, même si la pvchaî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 withsyntaxe récursive .

Efficacité

Pour les ensembles de données très volumineux, cette solution peut devenir lente, car l' find_in_setopé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 BYclause 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 idvaleurs 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 selectressemblerait à 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 sqlrequê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 wherecondition 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.

trincot
la source
26
J'aime bien ton explication. Il ne donne pas seulement une réponse, il explique pourquoi il résout le problème afin que nous puissions réellement en tirer des leçons. EDIT: c'est aussi bien qu'il ne repose pas sur la connaissance préalable du nombre de niveaux.
Byson
1
@Bison, j'apprécie beaucoup votre rétroaction. Merci!
trincot
2
@ Avión, ce n'est pas quelque chose que vous devez mettre quelque part, c'est une condition que pour tous les enregistrements cette condition soit vraie. Si vous avez un ou plusieurs enregistrements où parent_id > idvous ne pouvez pas utiliser cette solution.
trincot
2
Pour tous ceux qui cherchent à utiliser la WITH RECURSIVEméthode, j'ai trouvé l'article suivant vraiment utile avec différents scénarios tels que la profondeur de récursivité, les cycles distincts et de détection et de fermeture
Horse
2
J'ai essayé la solution principale sur MySQL5.7 sur mon ordinateur, sur mes propres tables, mais cela n'a pas fonctionné en raison de l'équivalent de la clause @pv: = concat (@pv, ',', id) a la valeur false. Je l'ai corrigé en le changeant en longueur (@pv: = concat (@pv, ',', id))> 0 donc c'est toujours vrai.
KC Wong
82

Depuis le blog Gestion des données hiérarchiques dans MySQL

Structure de la table

+-------------+----------------------+--------+
| category_id | name                 | parent |
+-------------+----------------------+--------+
|           1 | ELECTRONICS          |   NULL |
|           2 | TELEVISIONS          |      1 |
|           3 | TUBE                 |      2 |
|           4 | LCD                  |      2 |
|           5 | PLASMA               |      2 |
|           6 | PORTABLE ELECTRONICS |      1 |
|           7 | MP3 PLAYERS          |      6 |
|           8 | FLASH                |      7 |
|           9 | CD PLAYERS           |      6 |
|          10 | 2 WAY RADIOS         |      6 |
+-------------+----------------------+--------+

Requete:

SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS';

Production

+-------------+----------------------+--------------+-------+
| lev1        | lev2                 | lev3         | lev4  |
+-------------+----------------------+--------------+-------+
| ELECTRONICS | TELEVISIONS          | TUBE         | NULL  |
| ELECTRONICS | TELEVISIONS          | LCD          | NULL  |
| ELECTRONICS | TELEVISIONS          | PLASMA       | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS  | FLASH |
| ELECTRONICS | PORTABLE ELECTRONICS | CD PLAYERS   | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | 2 WAY RADIOS | NULL  |
+-------------+----------------------+--------------+-------+

La plupart des utilisateurs à un moment ou à un autre ont traité des données hiérarchiques dans une base de données SQL et ont sans aucun doute appris que la gestion des données hiérarchiques n'est pas ce à quoi une base de données relationnelle est destinée. Les tables d'une base de données relationnelle ne sont pas hiérarchiques (comme XML), mais sont simplement une liste plate. Les données hiérarchiques ont une relation parent-enfant qui n'est pas naturellement représentée dans une table de base de données relationnelle. Lire la suite

Consultez le blog pour plus de détails.

ÉDITER:

select @pv:=category_id as category_id, name, parent from category
join
(select @pv:=19)tmp
where parent=@pv

Production:

category_id name    parent
19  category1   0
20  category2   19
21  category3   20
22  category4   21

Référence: Comment faire la requête SELECT récursive dans Mysql?

Damodaran
la source
23
C'est bien tant qu'il n'y a pas plus de 4 niveaux au maximum dans la hiérarchie. S'il existe N niveaux, vous devez le savoir pour créer correctement la requête.
Jonathan Leffler
2
@Damodaran, Merci pour votre réponse ... Ce dont j'avais besoin est une condition où le nombre d'enfants n'est pas connu ... et dans le blog qui utilise un concept de jointure interne en ce que la hiérarchie doit être connue, ce qui n'est pas dans mon cas ... alors faites-moi connaître votre point de vue sur le même ... Donc, en termes simples, j'ai besoin d'une requête pour gérer les niveaux 'n' hirerachy où 'n' n'est pas connu .....
Tarun Parswani
1
@ user3036105: il n'est pas possible de le faire dans MySQL avec une seule requête SQL. MySQL n'est tout simplement pas assez avancé pour cela. Si vous en avez vraiment besoin, envisagez la mise à niveau vers un SGBD qui prend en charge les requêtes récursives.
a_horse_with_no_name
5
> La plupart des utilisateurs à un moment ou à un autre ont traité des données hiérarchiques dans une base de données SQL et ont sans aucun doute appris que la gestion des données hiérarchiques n'est pas ce à quoi une base de données relationnelle est destinée. Peut-être que vous vouliez dire une base de données MySQL. Une base de données Oracle gère assez bien les données et les requêtes hiérarchiques.
Peter Nosko du
1
"... la gestion des données hiérarchiques n'est pas ce à quoi une base de données relationnelle est destinée ..." Bien que cela n'ait peut-être pas été l'intention initiale d'une base de données relationnelle, dans le monde réel, les données hiérarchiques sont incroyablement courantes et MySQL devrait refléter comment les gens ont réellement besoin d'utiliser leurs données dans des scénarios réels.
Dave L
9

Essayez-les:

Définition du tableau:

DROP TABLE IF EXISTS category;
CREATE TABLE category (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(20),
    parent_id INT,
    CONSTRAINT fk_category_parent FOREIGN KEY (parent_id)
    REFERENCES category (id)
) engine=innodb;

Lignes expérimentales:

INSERT INTO category VALUES
(19, 'category1', NULL),
(20, 'category2', 19),
(21, 'category3', 20),
(22, 'category4', 21),
(23, 'categoryA', 19),
(24, 'categoryB', 23),
(25, 'categoryC', 23),
(26, 'categoryD', 24);

Procédure stockée récursive:

DROP PROCEDURE IF EXISTS getpath;
DELIMITER $$
CREATE PROCEDURE getpath(IN cat_id INT, OUT path TEXT)
BEGIN
    DECLARE catname VARCHAR(20);
    DECLARE temppath TEXT;
    DECLARE tempparent INT;
    SET max_sp_recursion_depth = 255;
    SELECT name, parent_id FROM category WHERE id=cat_id INTO catname, tempparent;
    IF tempparent IS NULL
    THEN
        SET path = catname;
    ELSE
        CALL getpath(tempparent, temppath);
        SET path = CONCAT(temppath, '/', catname);
    END IF;
END$$
DELIMITER ;

Fonction wrapper pour la procédure stockée:

DROP FUNCTION IF EXISTS getpath;
DELIMITER $$
CREATE FUNCTION getpath(cat_id INT) RETURNS TEXT DETERMINISTIC
BEGIN
    DECLARE res TEXT;
    CALL getpath(cat_id, res);
    RETURN res;
END$$
DELIMITER ;

Sélectionnez un exemple:

SELECT id, name, getpath(id) AS path FROM category;

Production:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 19 | category1 | category1                               |
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
| 23 | categoryA | category1/categoryA                     |
| 24 | categoryB | category1/categoryA/categoryB           |
| 25 | categoryC | category1/categoryA/categoryC           |
| 26 | categoryD | category1/categoryA/categoryB/categoryD |
+----+-----------+-----------------------------------------+

Filtrage des lignes avec un certain chemin:

SELECT id, name, getpath(id) AS path FROM category HAVING path LIKE 'category1/category2%';

Production:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
+----+-----------+-----------------------------------------+
Fandi Susanto
la source
1
Cela ne fonctionnera pas pour plus d'un enfant. par exemple(20, 'category2', 19), (21, 'category3', 20), (22, 'category4', 20),
Code propre
3
Je suis sûr que cela fonctionne pour plus d'un enfant. Je l'ai même testé à nouveau.
Fandi Susanto
@Fandi Susanto, merci beaucoup m'aide.
Dogan Ozer
9

La meilleure approche que j'ai trouvée est

  1. Utilisez la lignée pour stocker \ trier \ tracer les arbres. C'est plus que suffisant et cela fonctionne des milliers de fois plus rapidement pour la lecture que toute autre approche. Il permet également de rester sur ce modèle même si DB change (car N'IMPORTE QUELLE base de données permettra à ce modèle d'être utilisé)
  2. Utilisez la fonction qui détermine la lignée pour un ID spécifique.
  3. Utilisez-le comme vous le souhaitez (dans les sélections, ou sur les opérations CUD, ou même par travaux).

Approche de lignée descr. peut être trouvé partout, par exemple ici ou ici . En fonction - c'est ce qui m'a inspiré.

En fin de compte, la solution est devenue plus ou moins simple, relativement rapide et SIMPLE.

Corps de la fonction

-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$

CREATE DEFINER=`root`@`localhost` FUNCTION `get_lineage`(the_id INT) RETURNS text CHARSET utf8
    READS SQL DATA
BEGIN

 DECLARE v_rec INT DEFAULT 0;

 DECLARE done INT DEFAULT FALSE;
 DECLARE v_res text DEFAULT '';
 DECLARE v_papa int;
 DECLARE v_papa_papa int DEFAULT -1;
 DECLARE csr CURSOR FOR 
  select _id,parent_id -- @n:=@n+1 as rownum,T1.* 
  from 
    (SELECT @r AS _id,
        (SELECT @r := table_parent_id FROM table WHERE table_id = _id) AS parent_id,
        @l := @l + 1 AS lvl
    FROM
        (SELECT @r := the_id, @l := 0,@n:=0) vars,
        table m
    WHERE @r <> 0
    ) T1
    where T1.parent_id is not null
 ORDER BY T1.lvl DESC;
 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    open csr;
    read_loop: LOOP
    fetch csr into v_papa,v_papa_papa;
        SET v_rec = v_rec+1;
        IF done THEN
            LEAVE read_loop;
        END IF;
        -- add first
        IF v_rec = 1 THEN
            SET v_res = v_papa_papa;
        END IF;
        SET v_res = CONCAT(v_res,'-',v_papa);
    END LOOP;
    close csr;
    return v_res;
END

Et puis vous venez

select get_lineage(the_id)

J'espère que cela aide quelqu'un :)

Der Zinger
la source
9

A fait la même chose pour une autre quetion ici

Mysql select recursive get all child with multiple level

La requête sera:

SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM (
  SELECT @pv:=(
    SELECT GROUP_CONCAT(id SEPARATOR ',')
    FROM table WHERE parent_id IN (@pv)
  ) AS lv FROM table 
  JOIN
  (SELECT @pv:=1)tmp
  WHERE parent_id IN (@pv)
) a;
Dheerendra Kulkarni
la source
Comment peut-on le faire? SELECT idFolder, (SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM ( SELECT @pv:=(SELECT GROUP_CONCAT(idFolder SEPARATOR ',') FROM Folder WHERE idFolderParent IN (@pv)) AS lv FROM Folder JOIN (SELECT @pv:= F1.idFolder )tmp WHERE idFolderParent IN (@pv)) a) from folder F1 where id > 10; Je ne peux pas référencer F1.idFolder pour @pv
Rahul
J'ai recréé le tableau à partir de la question d'origine de OP avec les données comme indiqué dans leur commentaire, puis j'ai exécuté votre requête ici et j'en ai obtenu un simple NULL. Savez-vous pourquoi cela pourrait être? Y a-t-il des prérequis en termes de moteur de base de données, ou quelque chose a changé depuis que vous avez fait cette réponse qui rend cette requête obsolète?
Digital Ninja
7

Si vous avez besoin d'une vitesse de lecture rapide, la meilleure option est d'utiliser une table de fermeture. Une table de fermeture contient une ligne pour chaque paire ancêtre / descendant. Donc, dans votre exemple, la table de fermeture ressemblerait à

ancestor | descendant | depth
0        | 0          | 0
0        | 19         | 1
0        | 20         | 2
0        | 21         | 3
0        | 22         | 4
19       | 19         | 0
19       | 20         | 1
19       | 21         | 3
19       | 22         | 4
20       | 20         | 0
20       | 21         | 1
20       | 22         | 2
21       | 21         | 0
21       | 22         | 1
22       | 22         | 0

Une fois que vous avez ce tableau, les requêtes hiérarchiques deviennent très faciles et rapides. Pour obtenir tous les descendants de la catégorie 20:

SELECT cat.* FROM categories_closure AS cl
INNER JOIN categories AS cat ON cat.id = cl.descendant
WHERE cl.ancestor = 20 AND cl.depth > 0

Bien sûr, il y a un gros inconvénient lorsque vous utilisez des données dénormalisées comme celle-ci. Vous devez maintenir la table de fermeture à côté de votre table des catégories. La meilleure façon est probablement d'utiliser des déclencheurs, mais il est quelque peu complexe de suivre correctement les insertions / mises à jour / suppressions pour les tables de fermeture. Comme pour tout, vous devez examiner vos besoins et décider quelle approche vous convient le mieux.

Modifier : voir la question Quelles sont les options de stockage des données hiérarchiques dans une base de données relationnelle? pour plus d'options. Il existe différentes solutions optimales pour différentes situations.

Justin Howard
la source
4

Requête simple pour lister l'enfant de la première récursivité:

select @pv:=id as id, name, parent_id
from products
join (select @pv:=19)tmp
where parent_id=@pv

Résultat:

id  name        parent_id
20  category2   19
21  category3   20
22  category4   21
26  category24  22

... avec jointure gauche:

select
    @pv:=p1.id as id
  , p2.name as parent_name
  , p1.name name
  , p1.parent_id
from products p1
join (select @pv:=19)tmp
left join products p2 on p2.id=p1.parent_id -- optional join to get parent name
where p1.parent_id=@pv

La solution de @tincot pour lister tous les enfants:

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) > 0
and     @pv := concat(@pv, ',', id)

Testez-le en ligne avec Sql Fiddle et voyez tous les résultats.

http://sqlfiddle.com/#!9/a318e3/4/0

lynx_74
la source
3

Vous pouvez le faire comme ça dans d'autres bases de données assez facilement avec une requête récursive (YMMV sur les performances).

L'autre façon de le faire est de stocker deux bits de données supplémentaires, une valeur gauche et droite. Les valeurs gauche et droite sont dérivées d'un parcours de pré-commande de la structure arborescente que vous représentez.

Ceci est connu sous le nom de Traversal d'arbre de précommande modifié et vous permet d'exécuter une requête simple pour obtenir toutes les valeurs parentes en même temps. Il porte également le nom de "jeu imbriqué".

Phil John
la source
Je voulais ajouter un commentaire similaire au vôtre, mais puisque vous l'avez fait, je vais ajouter juste un lien vers un bon exemple de "l'ensemble imbriqué": mikehillyer.com/articles/managing-hierarchical-data-in-mysql
Miroslaw Opoka
2

Utilisez simplement la classe php BlueM / tree pour créer un arbre d'une table d'auto-relation dans mysql.

Tree et Tree \ Node sont des classes PHP pour gérer les données structurées de manière hiérarchique à l'aide de références d'ID parent. Un exemple typique est une table dans une base de données relationnelle où le champ «parent» de chaque enregistrement fait référence à la clé primaire d'un autre enregistrement. Bien sûr, Tree ne peut pas utiliser uniquement des données provenant d'une base de données, mais n'importe quoi: vous fournissez les données, et Tree les utilise, indépendamment de l'origine des données et de la façon dont elles ont été traitées. Lire la suite

Voici un exemple d'utilisation de BlueM / tree:

<?php 
require '/path/to/vendor/autoload.php'; $db = new PDO(...); // Set up your database connection 
$stm = $db->query('SELECT id, parent, title FROM tablename ORDER BY title'); 
$records = $stm->fetchAll(PDO::FETCH_ASSOC); 
$tree = new BlueM\Tree($records); 
...
Saleh Mosleh
la source
2

entrez la description de l'image ici

C'est un tableau des catégories .

SELECT  id,
        NAME,
        parent_category 
FROM    (SELECT * FROM category
         ORDER BY parent_category, id) products_sorted,
        (SELECT @pv := '2') initialisation
WHERE   FIND_IN_SET(parent_category, @pv) > 0
AND     @pv := CONCAT(@pv, ',', id)

Production:: entrez la description de l'image ici

Pradip Rupareliya
la source
1
Pouvez-vous expliquer cela? Mais je garantis que cela fonctionne. Je vous remercie.
wobsoriano
1
plz expliquer la requête et quel est le sens de @pv ?? Comment fonctionne la boucle dans cette requête?
Amanjot Kaur du
2
Ne semble pas fonctionner à tous les niveaux s'il y a des enfants qui ont des identifiants inférieurs à ceux de leurs parents. :(
Jonas
1
@Jonas m'a pris 20 minutes pour identifier le problème réel, en essayant avec une combinaison différente. Oui tu as raison. Il ne fonctionnera pas avec un ID inférieur à son ID parent. Avez-vous une solution?
muaaz
@muaaz Je l'ai finalement résolu en utilisant un champ "chemin" qui contient le chemin pour la ligne respective, par exemple la ligne avec l'ID 577 a le chemin "/ 1/2/45/577 /". Si vous recherchez tous les enfants de l'ID 2, vous pouvez simplement sélectionner toutes les lignes avec le chemin LIKE "/ 1/2 /%". Le seul inconvénient est que vous devez mettre à jour les chemins dans vos méthodes de mise à jour. Mais pour MySQL 5.6 (compatible), c'était la seule solution qui fonctionnait pour moi.
Jonas
1

C'est un peu délicat, vérifiez si cela fonctionne pour vous

select a.id,if(a.parent = 0,@varw:=concat(a.id,','),@varw:=concat(a.id,',',@varw)) as list from (select * from recursivejoin order by if(parent=0,id,parent) asc) a left join recursivejoin b on (a.id = b.parent),(select @varw:='') as c  having list like '%19,%';

Lien SQL fiddle http://www.sqlfiddle.com/#!2/e3cdf/2

Remplacez par votre nom de champ et de table de manière appropriée.

senK
la source
Cela ne fonctionnera pas dans ce cas sqlfiddle.com/#!2/19360/2 , avec cette astuce, au moins vous devez d'abord commander par niveau hiérarchique.
Jaugar Chang
1

Quelque chose qui n'est pas mentionné ici, bien qu'un peu similaire à la deuxième alternative de la réponse acceptée, mais différent et à faible coût pour les requêtes de grande hiérarchie et les éléments faciles (insérer la mise à jour), ajouterait une colonne de chemin persistant pour chaque élément.

certains, comme:

id | name        | path
19 | category1   | /19
20 | category2   | /19/20
21 | category3   | /19/20/21
22 | category4   | /19/20/21/22

Exemple:

-- get children of category3:
SELECT * FROM my_table WHERE path LIKE '/19/20/21%'
-- Reparent an item:
UPDATE my_table SET path = REPLACE(path, '/19/20', '/15/16') WHERE path LIKE '/19/20/%'

Optimisez la longueur du chemin et ORDER BY pathutilisez le codage base36 à la place de l'identifiant de chemin numérique réel

 // base10 => base36
 '1' => '1',
 '10' => 'A',
 '100' => '2S',
 '1000' => 'RS',
 '10000' => '7PS',
 '100000' => '255S',
 '1000000' => 'LFLS',
 '1000000000' => 'GJDGXS',
 '1000000000000' => 'CRE66I9S'

https://en.wikipedia.org/wiki/Base36

Suppression également du séparateur de barre oblique «/» en utilisant une longueur fixe et un remplissage à l'ID codé

Explication détaillée de l'optimisation ici: https://bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-materialized-path/

FAIRE

construction d'une fonction ou d'une procédure pour diviser le chemin d'accès pour retrouver les ancêtres d'un élément

MTK
la source
Merci! Intéressant avecbase36
Vlad
0

Cela fonctionne pour moi, j'espère que cela fonctionnera aussi pour vous. Il vous donnera un jeu d'enregistrements Root to Child pour tout menu spécifique. Modifiez le nom du champ selon vos besoins.

SET @id:= '22';

SELECT Menu_Name, (@id:=Sub_Menu_ID ) as Sub_Menu_ID, Menu_ID 
FROM 
    ( SELECT Menu_ID, Menu_Name, Sub_Menu_ID 
      FROM menu 
      ORDER BY Sub_Menu_ID DESC
    ) AS aux_table 
    WHERE Menu_ID = @id
     ORDER BY Sub_Menu_ID;
Monzur
la source
Ne semble pas fonctionner à tous les niveaux s'il y a des enfants qui ont une plus grande identité que leurs parents
muaaz
-1

Je l'ai trouvé plus facilement pour:

1) créer une fonction qui vérifiera si un élément se trouve n'importe où dans la hiérarchie parent d'un autre. Quelque chose comme ça (je n'écrirai pas la fonction, faites-la avec WHILE DO):

is_related(id, parent_id);

dans votre exemple

is_related(21, 19) == 1;
is_related(20, 19) == 1;
is_related(21, 18) == 0;

2) utilisez une sous-sélection, quelque chose comme ceci:

select ...
from table t
join table pt on pt.id in (select i.id from table i where is_related(t.id,i.id));
cripox
la source
-1

J'ai fait une requête pour vous. Cela vous donnera une catégorie récursive avec une seule requête:

SELECT id,NAME,'' AS subName,'' AS subsubName,'' AS subsubsubName FROM Table1 WHERE prent is NULL
UNION 
SELECT b.id,a.name,b.name AS subName,'' AS subsubName,'' AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id WHERE a.prent is NULL AND b.name IS NOT NULL 
UNION 
SELECT c.id,a.name,b.name AS subName,c.name AS subsubName,'' AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id LEFT JOIN Table1 AS c ON c.prent=b.id WHERE a.prent is NULL AND c.name IS NOT NULL 
UNION 
SELECT d.id,a.name,b.name AS subName,c.name AS subsubName,d.name AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id LEFT JOIN Table1 AS c ON c.prent=b.id LEFT JOIN Table1 AS d ON d.prent=c.id WHERE a.prent is NULL AND d.name IS NOT NULL 
ORDER BY NAME,subName,subsubName,subsubsubName

Voici un violon .

Manish
la source
Veuillez supprimer / modifier votre réponse pour retrouver votre réputation positive.
fWd82