Wie führe ich die rekursive SELECT-Abfrage in MySQL durch?

79

Ich habe folgende Tabelle:

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

Wenn ein Benutzer nach "1" sucht, sucht das Programm nach col1"1", dann erhält es einen Wert in col3"5", dann sucht das Programm weiter nach "5" in col1und erhält "3". in col3und so weiter. Also wird es ausgedruckt:

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

Wenn ein Benutzer nach "6" sucht, wird Folgendes ausgedruckt:

6   | o   | 2
2   | 0   | 8

Wie erstelle SELECTich eine Abfrage, um das zu tun?

Tum
quelle
Es gibt eine Lösung für Ihr Problem in diesem Beitrag stackoverflow.com/questions/14658378/recursive-mysql-select
medina

Antworten:

69

Bearbeiten

Die von @leftclickben erwähnte Lösung ist ebenfalls effektiv. Wir können auch eine gespeicherte Prozedur dafür verwenden.

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 //

Wir verwenden temporäre Tabellen, um die Ergebnisse der Ausgabe zu speichern, und da die temporären Tabellen sitzungsbasiert sind, gibt es keine Probleme mit falschen Ausgabedaten.

SQL FIDDLE Demo

Versuchen Sie diese Abfrage:

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 |

Der Hinweiswert
parent_id sollte kleiner sein als der, child_iddamit diese Lösung funktioniert.

Meherzad
quelle
2
Personen Bitte kennzeichnen Sie diese Antwort als optimale Lösung, da einige andere Lösungen mit ähnlichen Fragen (zu Recursive Select in MySQL) recht kompliziert sind, da eine Tabelle erstellt und Daten eingefügt werden müssen. Diese Lösung ist sehr elegant.
Tum
5
Seien Sie vorsichtig mit seiner Lösung, es gibt keine Abhängigkeit vom Zyklustyp, dann geht es in eine Endlosschleife und eine weitere Sache, es wird nur 1 Datensatz dieses col3Typs gefunden. Wenn es also mehrere Datensätze gibt, funktioniert es nicht.
Meherzad
2
@ HamidSarfraz jetzt funktioniert es sqlfiddle.com/#!2/74f457/14 . Dies wird für Sie funktionieren. Bei der sequentiellen Suche haben ID und ID immer einen höheren Wert als Parent, da Parent zuerst erstellt werden muss. Bitte informieren Sie, wenn Sie zusätzliche Details benötigen.
Meherzad
5
Dies ist keine Lösung. Es ist nur ein glücklicher Nebeneffekt eines Tabellenscans. Lesen Sie die Antwort von @leftclickben sorgfältig durch, sonst verschwenden Sie viel Zeit wie ich.
Jaeheung
2
Ich weiß, wie rekursives SQL funktioniert. MySQL hat keine rekursiven CTEs implementiert. Eine praktikable Option ist daher die in dem von Ihnen angegebenen Link (unter Verwendung gespeicherter Prozeduren / Funktionen). Ein anderer ist die Verwendung von MySQL-Variablen. Die Antwort hier ist jedoch nicht elegant, sondern das Gegenteil, einfach schrecklich. Es wird kein rekursives SQL angezeigt. Wenn es in Ihrem Fall funktioniert hat, war es nur ein Zufall, wie @jaehung richtig hervorhob. Und ich habe nichts gegen schreckliche Antworten. Ich habe sie nur abgelehnt. Aber eine schreckliche Antwort bei +50 macht mir etwas aus.
Ypercubeᵀᴹ
51

Die von @Meherzad akzeptierte Antwort funktioniert nur, wenn die Daten in einer bestimmten Reihenfolge vorliegen. Es funktioniert zufällig mit den Daten aus der OP-Frage. In meinem Fall musste ich es ändern, um mit meinen Daten zu arbeiten.

Hinweis Dies funktioniert nur, wenn die "ID" jedes Datensatzes (Spalte 1 in der Frage) einen Wert hat, der größer ist als die "Eltern-ID" des Datensatzes (Spalte 3 in der Frage). Dies ist häufig der Fall, da normalerweise zuerst das übergeordnete Element erstellt werden muss. Wenn Ihre Anwendung jedoch Änderungen an der Hierarchie zulässt, bei denen ein Element möglicherweise an einer anderen Stelle erneut übergeordnet wird, können Sie sich nicht darauf verlassen.

Dies ist meine Frage, falls es jemandem hilft; Beachten Sie, dass es mit der angegebenen Frage nicht funktioniert, da die Daten nicht der oben beschriebenen erforderlichen Struktur entsprechen.

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

Der Unterschied besteht darin, dass table1nach geordnet wird, col1damit der Elternteil hinter ihm her ist (da der col1Wert des Elternteils niedriger ist als der des Kindes).

leftclickben
quelle
u richtig, auch wenn ein Kind 2 Eltern hat, dann kann es nicht beide auswählen
Tum
Danke, Mann. Teamworek hat seine Tat in diesem Beitrag getan! Ich habe es zum Laufen gebracht, als ich den Wert von @pv geändert habe. Genau das habe ich gesucht.
Mohamed Ennahdi El Idrissi
Was ist, wenn ich dies als group_concat-Spalte mit übergeordneten IDs für jede Zeile in einer größeren Auswahl verwenden möchte (was bedeutet, dass der Wert der Variablen @pv für jede Zeile dynamisch sein soll). Die Join-in-Unterabfrage kennt die Master-Spalte (mit der ich eine Verbindung herstellen möchte) nicht. Mit einer anderen Variablen funktioniert sie auch nicht (gibt immer NULL zurück)
qdev
Ich habe eine benutzerdefinierte Funktion erstellt, die den Baumpfad mit group_concat generiert, und ich kann jetzt als Parameter den Spaltenwert für jede Zeile senden;)
qdev
Was denkst du über die neue Antwort, die ich gepostet habe? Nicht, dass deine nicht gut ist, aber ich wollte nur eine SELECT, die Eltern-ID> Kinder-ID unterstützt.
Meister DJon
18

Die Antwort von leftclickben funktionierte für mich, aber ich wollte einen Pfad von einem bestimmten Knoten zurück zum Baum zur Wurzel, und diese schienen in die andere Richtung zu gehen, den Baum hinunter. Also musste ich einige Felder umdrehen und aus Gründen der Klarheit umbenennen, und das funktioniert für mich, falls dies auch jemand anderes möchte -

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

und

select t.item_id as item, @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;

gibt:

item | parent
-------------
6    | 3
3    | 1
1    | null
BoB3K
quelle
@ BoB3K, würde dies funktionieren, wenn die IDs nicht unbedingt in "Reihenfolge" sind. Es scheint nicht zu funktionieren, wenn die ID eines Elternteils entlang der Kette höher ist als die seines Kindes? Beispielsweise gibt Kette 1> 120 > 112 nur ((112, 120)) zurück, während 2> 22> 221 die vollständige Kette ((221,22), (22,2), (2, null)) zurückgibt
Tomer Cagan
Es ist eine Weile her, aber ich denke, ich erinnere mich, dass ich in den ursprünglichen Antworten gelesen habe, dass dies nicht funktioniert, wenn die Artikel-IDs nicht in Ordnung sind. Dies ist normalerweise kein Problem, wenn die ID ein automatischer Inkrementierungsschlüssel ist.
BoB3K
Es funktioniert gut und ich benutze es für meine Site ... das Problem hier ist, dass es nicht möglich ist, die Ergebnisse ASC zu bestellen. 1 3 6Ich benutze array_reverse()stattdessen in PHP ..... eine SQL-Lösung dafür?
Joe
8

Gespeicherte Prozeduren sind der beste Weg, dies zu tun. Denn die Lösung von Meherzad würde nur funktionieren, wenn die Daten der gleichen Reihenfolge folgen.

Wenn wir eine solche Tabellenstruktur haben

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

Es wird nicht funktionieren. SQL Fiddle Demo

Hier ist ein Beispiel für einen Prozedurcode, um dasselbe zu erreichen.

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;
Jazmin
quelle
Dies ist eine robuste Lösung und ich benutze sie ohne Probleme. Können Sie mir bitte helfen, wenn Sie in die andere Richtung gehen, dh den Baum hinunter - ich finde alle Zeilen, in denen die übergeordnete ID == inputNo ist, aber viele IDs haben möglicherweise eine übergeordnete ID.
Mils
8

Wenn Sie eine SELECT-Funktion haben möchten, ohne dass die übergeordnete ID niedriger als die untergeordnete ID sein muss, kann eine Funktion verwendet werden. Es unterstützt auch mehrere Kinder (wie es ein Baum tun sollte) und der Baum kann mehrere Köpfe haben. Es wird auch sichergestellt, dass eine Unterbrechung auftritt, wenn eine Schleife in den Daten vorhanden ist.

Ich wollte dynamisches SQL verwenden, um die Tabellen- / Spaltennamen übergeben zu können, aber Funktionen in MySQL unterstützen dies nicht.

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$$

Hier muss die Tabelle testan den realen Tabellennamen angepasst werden und die Spalten (ParentId, Id) müssen möglicherweise an Ihre realen Namen angepasst werden.

Verwendung :

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

Ergebnis:

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

SQL für die Testerstellung:

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: Hier ist eine Geige , um es selbst zu testen. Es hat mich gezwungen, das Trennzeichen mit dem vordefinierten zu ändern, aber es funktioniert.

Meister DJon
quelle
0

Aufbauend auf Master DJon

Hier ist eine vereinfachte Funktion, die den zusätzlichen Nutzen der Rückgabe von Tiefe bietet (falls Sie die Logik verwenden möchten, um die übergeordnete Aufgabe einzuschließen oder in einer bestimmten Tiefe zu suchen).

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$$

Verwendung:

select * from test where childDepth(1, id) <> -1;
Patrick
quelle