web-dev-qa-db-fra.com

Comment faire la requête SELECT récursive dans MySQL?

J'ai un tableau suivant:

col1 | col2 | col3
-----+------+-------
1    | a    | 5
5    | d    | 3
3    | k    | 7
6    | o    | 2
2    | 0    | 8

Si un utilisateur recherche "1", le programme examinera le col1 qui a "1", il obtiendra une valeur dans col3 "5", puis le programme continuera à rechercher "5" dans col1 et il obtiendra "3" dans col3, et ainsi de suite. Donc, il va imprimer:

1   | a   | 5
5   | d   | 3
3   | k   | 7

Si un utilisateur recherche "6", il imprimera:

6   | o   | 2
2   | 0   | 8

Comment construire une requête SELECT pour le faire?

77
Tum

Modifier

La solution mentionnée par @leftclickben est également efficace. Nous pouvons également utiliser une procédure stockée pour la même chose.

CREATE PROCEDURE get_tree(IN id int)
 BEGIN
 DECLARE child_id int;
 DECLARE prev_id int;
 SET prev_id = id;
 SET child_id=0;
 SELECT col3 into child_id 
 FROM table1 WHERE col1=id ;
 create TEMPORARY  table IF NOT EXISTS temp_table as (select * from table1 where 1=0);
 truncate table temp_table;
 WHILE child_id <> 0 DO
   insert into temp_table select * from table1 WHERE col1=prev_id;
   SET prev_id = child_id;
   SET child_id=0;
   SELECT col3 into child_id
   FROM TABLE1 WHERE col1=prev_id;
 END WHILE;
 select * from temp_table;
 END //

Nous utilisons une table temporaire pour stocker les résultats de la sortie et, comme les tables temporaires sont basées sur la session, il n’y aura pas de problème de données incorrectes en sortie.

SQL FIDDLE Demo

Essayez cette requête:

SELECT 
    col1, col2, @pv := col3 as 'col3' 
FROM 
    table1
JOIN 
    (SELECT @pv := 1) tmp
WHERE 
    col1 = @pv

SQL FIDDLE Demo:

| COL1 | COL2 | COL3 |
+------+------+------+
|    1 |    a |    5 |
|    5 |    d |    3 |
|    3 |    k |    7 |

Remarque
parent_id doit être inférieur au child_id pour que cette solution fonctionne.

67
Meherzad

La réponse acceptée par @Meherzad ne fonctionne que si les données sont dans un ordre particulier. Il arrive de travailler avec les données de la question OP. Dans mon cas, je devais le modifier pour travailler avec mes données.

Remarque Ceci ne fonctionne que lorsque "id" de chaque enregistrement (col1 dans la question) a une valeur supérieure à "l'identifiant parent" de cet enregistrement (col3 dans la question). C'est souvent le cas, car normalement, le parent devra d'abord être créé. Toutefois, si votre application autorise les modifications de la hiérarchie, dans laquelle un élément peut être renommé ailleurs, vous ne pouvez pas vous en prévaloir.

Ceci est ma requête au cas où cela aiderait quelqu'un; notez que cela ne fonctionne pas avec la question donnée car les données ne suivent pas la structure requise décrite ci-dessus.

select t.col1, t.col2, @pv := t.col3 col3
from (select * from table1 order by col1 desc) t
join (select @pv := 1) tmp
where t.col1 = @pv

La différence est que table1 est commandé par col1 de sorte que le parent le suivra (puisque la valeur de col1 du parent est inférieure à celle de l'enfant).

50
leftclickben

la réponse de leftclickben a fonctionné pour moi, mais je voulais un chemin allant d'un nœud donné allant de l'arbre à la racine, et ceux-ci semblaient aller dans l'autre sens, en bas de l'arbre. Alors, j'ai dû retourner certains champs et les renommer pour plus de clarté, et cela fonctionne pour moi, au cas où c'est ce que quelqu'un d'autre voudrait aussi ...

item | parent
-------------
1    | null
2    | 1
3    | 1
4    | 2
5    | 4
6    | 3

et

select t.item_id as item_id, @pv:=t.parent as parent
from (select * from item_tree order by item_id desc) t
join
(select @pv:=6)tmp
where t.item_id=@pv;

donne:

item | parent
-------------
6    | 3
3    | 1
1    | null
16
BoB3K

La procédure stockée est la meilleure façon de le faire. Parce que la solution de Meherzad ne fonctionnerait que si les données suivent le même ordre.

Si nous avons une structure de table comme celle-ci

col1 | col2 | col3
-----+------+------
 3   | k    | 7
 5   | d    | 3
 1   | a    | 5
 6   | o    | 2
 2   | 0    | 8

Cela ne fonctionnera pas. SQL Fiddle Demo

Voici un exemple de code de procédure pour obtenir le même résultat.

delimiter //
CREATE PROCEDURE chainReaction 
(
    in inputNo int
) 
BEGIN 
    declare final_id int default NULL;
    SELECT col3 
    INTO final_id 
    FROM table1
    WHERE col1 = inputNo;
    IF( final_id is not null) THEN
        INSERT INTO results(SELECT col1, col2, col3 FROM table1 WHERE col1 = inputNo);
        CALL chainReaction(final_id);   
    end if;
END//
delimiter ;

call chainReaction(1);
SELECT * FROM results;
DROP TABLE if exists results;
7
Jazmin

Si vous voulez pouvoir avoir une commande SELECT sans que le problème de l'identifiant parent soit inférieur à celui de l'enfant, une fonction peut être utilisée. Il prend également en charge plusieurs enfants (comme un arbre devrait le faire) et l’arbre peut avoir plusieurs têtes. Cela garantit également la rupture si une boucle existe dans les données.

Je voulais utiliser le SQL dynamique pour pouvoir passer les noms de table/colonnes, mais les fonctions de MySQL ne le supportent pas.

DELIMITER $$

CREATE FUNCTION `isSubElement`(pParentId INT, pId INT) RETURNS int(11)
DETERMINISTIC    
READS SQL DATA
BEGIN
DECLARE isChild,curId,curParent,lastParent int;
SET isChild = 0;
SET curId = pId;
SET curParent = -1;
SET lastParent = -2;

WHILE lastParent <> curParent AND curParent <> 0 AND curId <> -1 AND curParent <> pId AND isChild = 0 DO
    SET lastParent = curParent;
    SELECT ParentId from `test` where id=curId limit 1 into curParent;

    IF curParent = pParentId THEN
        SET isChild = 1;
    END IF;
    SET curId = curParent;
END WHILE;

RETURN isChild;
END$$

Ici, la table test doit être modifiée en nom de table réel et les colonnes (ParentId, Id) doivent être ajustées pour vos noms réels.

Utilisation:

SET @wantedSubTreeId = 3;
SELECT * FROM test WHERE isSubElement(@wantedSubTreeId,id) = 1 OR ID = @wantedSubTreeId;

Résultat :

3   7   k
5   3   d
9   3   f
1   5   a

SQL pour la création de test:

CREATE TABLE IF NOT EXISTS `test` (
  `Id` int(11) NOT NULL,
  `ParentId` int(11) DEFAULT NULL,
  `Name` varchar(300) NOT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1;

insert into test (id, parentid, name) values(3,7,'k');
insert into test (id, parentid, name) values(5,3,'d');
insert into test (id, parentid, name) values(9,3,'f');
insert into test (id, parentid, name) values(1,5,'a');
insert into test (id, parentid, name) values(6,2,'o');
insert into test (id, parentid, name) values(2,8,'c');

EDIT: Voici un violon pour le tester vous-même. Cela m'a obligé à changer le délimiteur en utilisant celui prédéfini, mais cela fonctionne.

6
Master DJon

Construire à partir de Master DJon

Voici une fonction simplifiée qui fournit l’utilité supplémentaire de retourner la profondeur (au cas où vous voudriez utiliser une logique pour inclure la tâche parente ou effectuer une recherche à une profondeur spécifique)

DELIMITER $$
FUNCTION `childDepth`(pParentId INT, pId INT) RETURNS int(11)
    READS SQL DATA
    DETERMINISTIC
BEGIN
DECLARE depth,curId int;
SET depth = 0;
SET curId = pId;

WHILE curId IS not null AND curId <> pParentId DO
    SELECT ParentId from test where id=curId limit 1 into curId;
    SET depth = depth + 1;
END WHILE;

IF curId IS NULL THEN
    set depth = -1;
END IF;

RETURN depth;
END$$

Usage:

select * from test where childDepth(1, id) <> -1;
0
patrick