J'ai une table MySQL qui est 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, dites 'id = 19'], puis je devrais obtenir tous ses identifiants enfants [i.e. le résultat devrait avoir les identifiants '20, 21,22 '] .... De plus, 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 réaliser la même chose en utilisant une seule requête MySQL, si possible.
Si vous êtes sur MySQL 8, utilisez la clause récursive with
:
_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 id
du parent dont vous souhaitez sélectionner tous les descendants.
Pour les versions de MySQL qui ne prennent pas en charge les expressions de table communes (jusqu'à la version 5.7), réalisez ceci 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 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 réponde à la condition _parent_id < id
_, sinon les résultats ne seront pas complets.
Cette requête utilise une syntaxe MySQL spécifique: les variables sont attribuées et modifiées lors de son exécution. Certaines hypothèses sont faites sur l'ordre d'exécution:
from
est évaluée en premier. C’est donc là que _@pv
_ est initialisé.where
est évaluée pour chaque enregistrement dans l'ordre d'extraction des alias from
. Il s'agit donc d'une condition qui n'inclut que les enregistrements pour lesquels le parent a déjà été identifié comme étant dans l'arbre des descendants (tous les descendants du parent principal sont progressivement ajoutés à _@pv
_).where
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 id
à la liste parente, ce qui ne devrait se produire que si id
réussit la première condition. La fonction length
est uniquement appelée pour s'assurer que cette condition est toujours vraie, même si la chaîne pv
devait pour une raison quelconque donner une valeur falsy.Au total, on peut trouver ces hypothèses trop risquées. Le documentation avertit:
vous pouvez 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 en tant que vue ou sous-requête dans une requête plus grande. C'est une "fonctionnalité" qui sera supprimée dans une prochaine 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é ascendante, mais peut être supprimée dans une version ultérieure de MySQL.
Comme indiqué ci-dessus, à partir de MySQL 8.0, vous devez utiliser la syntaxe récursive with
.
Pour les très grands ensembles de données, cette solution peut être lente, car l'opération find_in_set
n'est pas la méthode la plus idéale pour rechercher un nombre dans une liste, et certainement pas dans une liste atteignant une taille inférieure à. le même ordre de grandeur que le nombre d'enregistrements retournés.
with recursive
_, _connect by
_De plus en plus de bases de données implémentent la syntaxe SQL: 1999 ISO standard _WITH [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 version 8.0, MySQL le supporte également . 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, telle que la clause _CONNECT BY
_ disponible sur Oracle , DB2 , Informix , CUBRID et 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 une autre qui le fait, c'est certainement la meilleure option. Si non, alors considérez également les alternatives suivantes.
Les choses deviennent beaucoup plus faciles si vous affectez des valeurs id
contenant 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/%'
_
Si vous connaissez une limite supérieure pour la profondeur de votre arborescence hiérarchique, vous pouvez utiliser une requête standard sql
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 ceci violon
La condition where
spécifie le parent dont vous voulez récupérer les descendants. Vous pouvez étendre cette requête avec plus de niveaux si nécessaire.
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';
Sortie
+-------------+----------------------+--------------+-------+
| 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’était pas destinée à une base de données relationnelle. Les tables d'une base de données relationnelle ne sont pas hiérarchiques (comme XML), mais simplement une liste à plat. 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. en savoir plus
Référez-vous au blog pour plus de détails.
EDIT:
select @pv:=category_id as category_id, name, parent from category
join
(select @pv:=19)tmp
where parent=@pv
Sortie:
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?
Essayez ces:
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;
Rangées 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;
Sortie:
+----+-----------+-----------------------------------------+
| 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 certains chemins:
SELECT id, name, getpath(id) AS path FROM category HAVING path LIKE 'category1/category2%';
Sortie:
+----+-----------+-----------------------------------------+
| id | name | path |
+----+-----------+-----------------------------------------+
| 20 | category2 | category1/category2 |
| 21 | category3 | category1/category2/category3 |
| 22 | category4 | category1/category2/category3/category4 |
+----+-----------+-----------------------------------------+
A fait la même chose pour une autre question ici
Mysql select recursive obtient tous les enfants avec plusieurs niveaux
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;
La meilleure approche que je suis venu avec est
Approche de la lignée descr. peut être trouvé partout, par exemple Ici ou ici . En ce qui concerne la fonction - that c'est ce qui m'a inspiré.
En fin de compte - obtenu une solution 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 tu viens
select get_lineage(the_id)
J'espère que ça aide quelqu'un :)
Si vous avez besoin d'une vitesse de lecture rapide, la meilleure option consiste à utiliser une table de fermeture. Une table de fermeture contient une ligne pour chaque couple 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 cette table, 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 de catégories. Le meilleur moyen est probablement d'utiliser des déclencheurs, mais il est assez 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.
Éditer : Voir la question Quelles sont les options pour stocker 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.
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
... à gauche rejoindre:
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.
Vous pouvez le faire comme ceci 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 left et right sont dérivées d'une traversée pré-ordre de l'arborescence que vous représentez.
Cette opération est connue sous le nom de Traversée de l'arborescence des commandes en attente modifiée et vous permet d'exécuter une requête simple pour obtenir toutes les valeurs parent en même temps. Il porte également le nom "jeu imbriqué".
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 de violon SQL http://www.sqlfiddle.com/#!2/e3cdf/2
Remplacez avec votre nom de champ et de table de manière appropriée.
Il suffit d'utiliser BlueM/tree La classe php pour créer un arbre d'une table d'auto-relation dans mysql.
Tree et Tree\Node sont des classes PHP permettant de traiter des données structurées hiérarchiquement à 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 les 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. en savoir plus
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);
...
Quelque chose non mentionné ici, bien que légèrement similaire à la deuxième alternative de la réponse acceptée, mais différent et peu coûteux pour les requêtes de grande hiérarchie et les éléments faciles (insérer une suppression de mise à jour), consisterait à ajouter 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/%'
Optimise la longueur du chemin et ORDER BY path
en utilisant un codage en base36 à la place d'un identificateur de chemin numérique
// 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
Supprimer également le séparateur de barre oblique '/' en utilisant une longueur fixe et un remplissage avec l'id codé
Explication détaillée de l'optimisation ici: https://bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-materialized-path/
TODO
construction d'une fonction ou d'une procédure pour diviser le chemin pour retrouver les ancêtres d'un élément
Cela fonctionne pour moi, espérons que cela fonctionnera pour vous aussi. Il vous donnera un ensemble d’enregistrements Racine à Enfant pour tout menu spécifique. Changez 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;
Je l'ai trouvé plus facilement pour:
1) créer une fonction qui vérifiera si un élément est n'importe où dans la hiérarchie parentale d'un autre. Quelque chose comme ça (je ne vais pas écrire 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));