So erstellen Sie eine hierarchische rekursive MySQL-Abfrage


271

Ich habe eine MySQL-Tabelle, die wie folgt lautet:

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

Jetzt möchte ich eine einzelne MySQL-Abfrage haben, zu der ich einfach die ID gebe [zum Beispiel 'id = 19'], dann sollte ich alle untergeordneten IDs erhalten [dh das Ergebnis sollte die IDs '20, 21,22 'haben]. ... Auch die Hierarchie der Kinder ist nicht bekannt, sie kann variieren ....

Außerdem habe ich bereits die Lösung mit der for-Schleife ..... Lassen Sie mich wissen, wie Sie dies mit einer einzigen MySQL-Abfrage erreichen können, wenn dies möglich ist.


Angenommen, die Hierarchie ist 7 Ebenen tief. Wie soll die Ausgabetabelle aussehen?
Jonathan Leffler

1
MySQL unterstützt (noch) keine hierarchischen Abfragen (wie andere moderne DBMS). Sie müssen eine gespeicherte Prozedur schreiben oder ein anderes Datenmodell verwenden.
a_horse_with_no_name


1
MYSQL 8.0 unterstützt rekursive Abfragen mit CTE (Common Table Expressions)
user3712320

Wie wäre es, wenn Sie die vollständige Liste der Beiträge ab der letzten Kommentar-ID erhalten? Oder das letzte Kind?
Joe

Antworten:


392

Für MySQL 8+: Verwenden Sie die rekursive withSyntax.
Für MySQL 5.x: Verwenden Sie Inline-Variablen, Pfad-IDs oder Self-Joins.

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;

Der in angegebene Wert parent_id = 19sollte auf den Wert iddes übergeordneten Elements festgelegt werden, von dem Sie alle Nachkommen auswählen möchten.

MySQL 5.x.

Für MySQL-Versionen, die Common Table Expressions (bis Version 5.7) nicht unterstützen, erreichen Sie dies mit der folgenden Abfrage:

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

Hier ist eine Geige .

Hier sollte der in angegebene Wert auf den Wert des übergeordneten Elements festgelegt @pv := '19'werden, idvon dem Sie alle Nachkommen auswählen möchten.

Dies funktioniert auch, wenn ein Elternteil mehrere Kinder hat. Es ist jedoch erforderlich, dass jeder Datensatz die Bedingung erfüllt parent_id < id, da sonst die Ergebnisse nicht vollständig sind.

Variablenzuweisungen innerhalb einer Abfrage

Diese Abfrage verwendet eine bestimmte MySQL-Syntax: Variablen werden während ihrer Ausführung zugewiesen und geändert. Es werden einige Annahmen über die Reihenfolge der Ausführung getroffen:

  • Die fromKlausel wird zuerst ausgewertet. Hier @pvwird also initialisiert.
  • Die whereKlausel wird für jeden Datensatz in der Reihenfolge des Abrufs aus den fromAliasen ausgewertet . Hier wird also eine Bedingung gestellt, die nur Datensätze enthält, für die das übergeordnete Element bereits als im Nachkommenbaum identifiziert wurde (alle Nachkommen des primären übergeordneten Elements werden schrittweise hinzugefügt @pv).
  • Die Bedingungen in dieser whereKlausel werden der Reihe nach bewertet, und die Bewertung wird unterbrochen, sobald das Gesamtergebnis sicher ist. Daher muss die zweite Bedingung an zweiter Stelle stehen, da sie die idzur übergeordneten Liste hinzufügt , und dies sollte nur geschehen, wenn iddie erste Bedingung erfüllt ist. Die lengthFunktion wird nur aufgerufen, um sicherzustellen, dass diese Bedingung immer erfüllt ist, auch wenn die pvZeichenfolge aus irgendeinem Grund einen falschen Wert ergeben würde.

Alles in allem kann man diese Annahmen als zu riskant empfinden, um sich darauf zu verlassen. Die Dokumentation warnt:

Möglicherweise erhalten Sie die erwarteten Ergebnisse, dies ist jedoch nicht garantiert. [...] Die Reihenfolge der Auswertung für Ausdrücke mit Benutzervariablen ist nicht definiert.

Obwohl dies konsistent mit der obigen Abfrage funktioniert, kann sich die Auswertungsreihenfolge dennoch ändern, z. B. wenn Sie Bedingungen hinzufügen oder diese Abfrage als Ansicht oder Unterabfrage in einer größeren Abfrage verwenden. Es ist eine "Funktion", die in einer zukünftigen MySQL-Version entfernt wird :

In früheren Versionen von MySQL war es möglich, einer Benutzervariablen in anderen Anweisungen als einen Wert zuzuweisen SET. Diese Funktionalität wird aus Gründen der Abwärtskompatibilität in MySQL 8.0 unterstützt, muss jedoch in einer zukünftigen Version von MySQL entfernt werden.

Wie oben erwähnt, sollten Sie ab MySQL 8.0 die rekursive withSyntax verwenden.

Effizienz

Bei sehr großen Datenmengen kann diese Lösung langsam werden, da der find_in_setVorgang nicht der idealste Weg ist, um eine Zahl in einer Liste zu finden, schon gar nicht in einer Liste, deren Größe in der Größenordnung der Anzahl der zurückgegebenen Datensätze liegt.

Alternative 1: with recursive,connect by

Immer mehr Datenbanken , um die Implementierung von SQL: 1999 ISO - Standard - WITH [RECURSIVE]Syntax für rekursive Abfragen (zB 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+ ). Und ab Version 8.0 unterstützt es auch MySQL . Die zu verwendende Syntax finden Sie oben in dieser Antwort.

Einige Datenbanken verfügen über eine alternative, nicht standardmäßige Syntax für hierarchische Suchvorgänge, z. B. die CONNECT BYKlausel, die in Oracle , DB2 , Informix , CUBRID und anderen Datenbanken verfügbar ist .

MySQL Version 5.7 bietet eine solche Funktion nicht. Wenn Ihr Datenbankmodul diese Syntax bereitstellt oder Sie zu einer solchen migrieren können, ist dies sicherlich die beste Option. Wenn nicht, ziehen Sie auch die folgenden Alternativen in Betracht.

Alternative 2: Pfadkennungen

Die Dinge werden viel einfacher, wenn Sie idWerte zuweisen , die die hierarchischen Informationen enthalten: einen Pfad. In Ihrem Fall könnte dies beispielsweise so aussehen:

ID       | NAME
19       | category1   
19/1     | category2  
19/1/1   | category3  
19/1/1/1 | category4  

Dann selectwürden Sie so aussehen:

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

Alternative 3: Wiederholte Selbstverbindungen

Wenn Sie eine Obergrenze für die Tiefe Ihres Hierarchiebaums kennen, können Sie eine Standardabfrage sqlwie die folgende verwenden:

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;

Sehen Sie diese Geige

Die whereBedingung gibt an, von welchem ​​Elternteil Sie die Nachkommen abrufen möchten. Sie können diese Abfrage nach Bedarf um weitere Ebenen erweitern.


26
Ich mag deine Erklärung. Es gibt nicht nur eine Antwort, es erklärt auch, warum es das Problem löst, damit wir tatsächlich daraus lernen können. EDIT: Es ist auch großartig, dass es nicht darauf ankommt, die Anzahl der Levels im Voraus zu kennen.
Byson

1
@ Bison, ich freue mich sehr über Ihr Feedback. Vielen Dank!
Trincot

2
@ Avión, es ist nicht etwas, das Sie irgendwo platzieren müssen, es ist eine Voraussetzung, dass für alle Datensätze diese Bedingung wahr ist. Wenn Sie einen oder mehrere Datensätze haben parent_id > id, können Sie diese Lösung nicht verwenden.
Trincot

2
Für alle, die diese WITH RECURSIVEMethode
Pferd

2
Ich habe die Hauptlösung unter MySQL5.7 auf meinem Computer in meinen eigenen Tabellen ausprobiert, aber das hat nicht funktioniert, da die Klausel @pv: = concat (@pv, ',', id) als false ausgewertet wird. Ich habe es behoben, indem ich es auf Länge geändert habe (@pv: = concat (@pv, ',', id))> 0, damit es immer wahr ist.
KC Wong

82

Aus dem Blog Verwalten hierarchischer Daten in MySQL

Tabellenstruktur

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

Abfrage:

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';

Ausgabe

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

Die meisten Benutzer haben sich zu der einen oder anderen Zeit mit hierarchischen Daten in einer SQL-Datenbank befasst und zweifellos erfahren, dass die Verwaltung hierarchischer Daten nicht das ist, wofür eine relationale Datenbank gedacht ist. Die Tabellen einer relationalen Datenbank sind nicht hierarchisch (wie XML), sondern lediglich eine flache Liste. Hierarchische Daten haben eine Eltern-Kind-Beziehung, die in einer relationalen Datenbanktabelle natürlich nicht dargestellt wird. Weiterlesen

Weitere Informationen finden Sie im Blog.

BEARBEITEN:

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

Ausgabe:

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

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


23
Das ist in Ordnung, solange die Hierarchie höchstens 4 Ebenen enthält. Wenn es N Ebenen gibt, müssen Sie dies wissen, um die Abfrage ordnungsgemäß zu erstellen.
Jonathan Leffler

2
@Damodaran, danke für deine Antwort ... Was ich brauchte, ist eine Bedingung, bei der die Anzahl der Kinder nicht bekannt ist ... und in dem Blog, das ein Inner-Join-Konzept verwendet, muss die Hierarchie bekannt sein, was nicht der Fall ist in meinem Fall ... also lassen Sie mich Ihre Meinung dazu wissen ... Also, in einfachen Worten, ich brauche eine Abfrage, um 'n' Hirerachy-Levels zu behandeln, bei denen 'n' nicht bekannt ist .....
Tarun Parswani

1
@ user3036105: Dies ist in MySQL mit einer einzelnen SQL-Abfrage nicht möglich . MySQL ist dafür einfach nicht fortgeschritten genug. Wenn Sie dies wirklich benötigen, sollten Sie ein Upgrade auf ein DBMS in Betracht ziehen, das rekursive Abfragen unterstützt.
a_horse_with_no_name

5
> Die meisten Benutzer haben sich zu der einen oder anderen Zeit mit hierarchischen Daten in einer SQL-Datenbank befasst und zweifellos festgestellt, dass die Verwaltung hierarchischer Daten nicht das Ziel einer relationalen Datenbank ist. Vielleicht haben Sie eine MySQL-Datenbank gemeint. Eine Oracle-Datenbank verarbeitet hierarchische Daten und Abfragen recht gut.
Peter Nosko

1
"... die Verwaltung hierarchischer Daten ist nicht das, wofür eine relationale Datenbank bestimmt ist ..." Während dies möglicherweise nicht die ursprüngliche Absicht einer relationalen Datenbank war, sind hierarchische Daten in der realen Welt unglaublich alltäglich und MySQL sollte dies widerspiegeln wie Menschen ihre Daten tatsächlich in realen Szenarien verwenden müssen.
Dave L

9

Probiere diese:

Tabellendefinition:

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;

Versuchsreihen:

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);

Rekursiv gespeicherte Prozedur:

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 ;

Wrapper-Funktion für die gespeicherte Prozedur:

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 ;

Beispiel auswählen:

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

Ausgabe:

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

Filtern von Zeilen mit einem bestimmten Pfad:

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

Ausgabe:

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

1
Dies funktioniert nicht für mehr als ein Kind. zB(20, 'category2', 19), (21, 'category3', 20), (22, 'category4', 20),
Clean Code

3
Ich bin mir ziemlich sicher, dass es für mehr als ein Kind funktioniert. Ich habe es sogar noch einmal getestet.
Fandi Susanto

@Fandi Susanto, danke, es hilft mir.
Dogan Ozer

9

Der beste Ansatz, den ich mir ausgedacht habe, ist

  1. Verwenden Sie die Abstammung, um \ sort \ trace-Bäume zu speichern. Das ist mehr als genug und funktioniert beim Lesen tausendmal schneller als jeder andere Ansatz. Es erlaubt auch, auf diesem Muster zu bleiben, selbst wenn sich DB ändert (da JEDE Datenbank die Verwendung dieses Musters zulässt)
  2. Verwenden Sie eine Funktion, die die Abstammung für eine bestimmte ID bestimmt.
  3. Verwenden Sie es nach Ihren Wünschen (in ausgewählten, CUD-Vorgängen oder sogar nach Jobs).

Abstammungsansatz Beschreibung kann überall gefunden werden, zum Beispiel hier oder hier . Ab Funktion - das , was mich ist enspired.

Am Ende - mehr oder weniger einfache, relativ schnelle und EINFACHE Lösung.

Funktionskörper

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

Und dann bist du einfach

select get_lineage(the_id)

Hoffe es hilft jemandem :)


9

Habe das Gleiche für eine weitere Frage hier getan

MySQL auswählen rekursiv alle Kinder mit mehreren Ebenen erhalten

Die Abfrage lautet:

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;

Wie können wir das machen? 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;; Ich kann F1.idFolder nicht für @pv
Rahul

Ich habe die Tabelle aus der ursprünglichen Frage von OP mit den in ihrem Kommentar angegebenen Daten neu erstellt, dann Ihre Abfrage hier ausgeführt und NULLals Ergebnis eine einzelne erhalten . Weißt du warum das sein könnte? Gibt es Voraussetzungen für das Datenbankmodul oder hat sich etwas geändert, seit Sie diese Antwort gegeben haben, die diese Abfrage veraltet macht?
Digital Ninja

7

Wenn Sie eine schnelle Lesegeschwindigkeit benötigen, verwenden Sie am besten eine Verschlusstabelle. Eine Schließungstabelle enthält eine Zeile für jedes Ahnen / Nachkommen-Paar. In Ihrem Beispiel würde der Verschlusstisch also so aussehen

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

Sobald Sie diese Tabelle haben, werden hierarchische Abfragen sehr einfach und schnell. So erhalten Sie alle Nachkommen der Kategorie 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

Natürlich gibt es einen großen Nachteil, wenn Sie denormalisierte Daten wie diese verwenden. Sie müssen die Schließungstabelle neben Ihrer Kategorietabelle pflegen. Der beste Weg ist wahrscheinlich die Verwendung von Triggern, aber es ist etwas komplex, Einfügungen / Aktualisierungen / Löschungen für Schließungstabellen korrekt zu verfolgen. Wie bei allem müssen Sie sich Ihre Anforderungen ansehen und entscheiden, welcher Ansatz für Sie am besten geeignet ist.

Bearbeiten : Siehe Frage Welche Optionen gibt es zum Speichern hierarchischer Daten in einer relationalen Datenbank? für mehr Optionen. Es gibt verschiedene optimale Lösungen für verschiedene Situationen.


4

Einfache Abfrage zum Auflisten von Kindern der ersten Rekursion:

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

Ergebnis:

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

... mit linkem Join:

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

Die Lösung von @tincot, um alle Kinder aufzulisten:

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)

Testen Sie es online mit Sql Fiddle und sehen Sie alle Ergebnisse.

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


3

Sie können dies in anderen Datenbanken ganz einfach mit einer rekursiven Abfrage (YMMV on Performance) tun.

Die andere Möglichkeit besteht darin, zwei zusätzliche Datenbits zu speichern, einen linken und einen rechten Wert. Der linke und der rechte Wert werden aus einer Vorbestellungsdurchquerung der Baumstruktur abgeleitet, die Sie darstellen.

Dies wird als Modified Preorder Tree Traversal bezeichnet und ermöglicht es Ihnen, eine einfache Abfrage auszuführen, um alle übergeordneten Werte gleichzeitig abzurufen. Es trägt auch den Namen "verschachtelte Menge".


Ich wollte einen ähnlichen Kommentar zu Ihrem hinzufügen, aber da Sie dies getan haben, werde ich nur einen Link zu einem guten Beispiel für die "verschachtelte Menge" hinzufügen
Miroslaw Opoka

2

Verwenden Sie einfach die BlueM / Tree- PHP-Klasse, um einen Baum aus einer Selbstbeziehungstabelle in MySQL zu erstellen .

Tree und Tree \ Node sind PHP-Klassen für die Verarbeitung von Daten, die hierarchisch unter Verwendung von übergeordneten ID-Referenzen strukturiert sind. Ein typisches Beispiel ist eine Tabelle in einer relationalen Datenbank, in der das übergeordnete Feld jedes Datensatzes auf den Primärschlüssel eines anderen Datensatzes verweist. Natürlich kann Tree nicht nur Daten aus einer Datenbank verwenden, sondern alles: Sie geben die Daten an, und Tree verwendet sie, unabhängig davon, woher die Daten stammen und wie sie verarbeitet wurden. Weiterlesen

Hier ist ein Beispiel für die Verwendung von 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); 
...

2

Geben Sie hier die Bildbeschreibung ein

Es ist eine Kategorietabelle .

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)

Ausgabe:: Geben Sie hier die Bildbeschreibung ein


Kannst du das erklären? Aber ich garantiere, dass dies funktioniert. Danke dir.
Wobsoriano

PLZ erklären die Abfrage und was ist die Bedeutung von @pv? Wie funktioniert die Schleife in dieser Abfrage?
Amanjot Kaur

1
Scheint nicht auf allen Ebenen zu funktionieren, wenn es Kinder gibt, die einen niedrigeren Personalausweis als ihre Eltern haben. :(
Jonas

1
@ Jonas brauchte 20 Minuten, um das eigentliche Problem zu identifizieren und versuchte es mit einer anderen Kombination. ja, du hast Recht. Es funktioniert nicht mit einer ID, die niedriger als die übergeordnete ID ist. Hast du eine Lösung?
Muaaz

@muaaz Ich habe es schließlich mit einem "Pfad" -Feld gelöst, das den Pfad für die jeweilige Zeile enthält, z. B. hat die Zeile mit der ID 577 den Pfad "/ 1/2/45/577 /". Wenn Sie nach allen untergeordneten Elementen der ID 2 suchen, können Sie einfach alle Zeilen mit dem Pfad LIKE "/ 1/2 /%" auswählen. Der einzige Nachteil ist, dass Sie die Pfade in Ihren Aktualisierungsmethoden aktualisieren müssen. Aber für MySQL 5.6 (kompatibel) war es die einzige Lösung, die für mich funktioniert hat.
Jonas

1

Es ist ein wenig knifflig, prüfen Sie, ob es für Sie funktioniert

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,%';

SQL Fiddle Link http://www.sqlfiddle.com/#!2/e3cdf/2

Ersetzen Sie Ihren Feld- und Tabellennamen entsprechend.


In diesem Fall funktioniert es nicht. Sqlfiddle.com/#!2/19360/2 , mit diesem Trick sollten Sie zumindest zuerst nach Hierarchieebene ordnen.
Jaugar Chang

1

Etwas, das hier nicht erwähnt wird, obwohl es der zweiten Alternative der akzeptierten Antwort etwas ähnlich ist, aber für große Hierarchie-Abfragen und einfache (Einfügen, Aktualisieren, Löschen) Elemente unterschiedlich und kostengünstig ist, würde für jedes Element eine dauerhafte Pfadspalte hinzufügen.

einige wie:

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

Beispiel:

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

Optimieren Sie die Pfadlänge und ORDER BY pathverwenden Sie stattdessen die Base36-Codierung anstelle der reellen numerischen Pfad-ID

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

Unterdrücken Sie auch das Schrägstrich-Trennzeichen '/', indem Sie eine feste Länge verwenden und die codierte ID auffüllen

Detaillierte Erläuterungen zur Optimierung finden Sie hier: https://bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-materialized-path/

MACHEN

Erstellen einer Funktion oder Prozedur zum Aufteilen des Pfads für zurückhaltende Vorfahren eines Elements


Vielen Dank! Interessant mitbase36
Vlad

0

Das funktioniert bei mir, hoffe, das funktioniert auch bei Ihnen. Sie erhalten einen Datensatzsatz Root to Child für ein bestimmtes Menü. Ändern Sie den Feldnamen gemäß Ihren Anforderungen.

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;

Scheint nicht auf allen Ebenen zu funktionieren, wenn es Kinder gibt, die einen größeren
Ausweis

-1

Ich fand es leichter:

1) Erstellen Sie eine Funktion, die prüft, ob sich ein Element an einer beliebigen Stelle in der übergeordneten Hierarchie eines anderen Elements befindet. So etwas wie das (ich werde die Funktion nicht schreiben, mache es mit WHILE DO):

is_related(id, parent_id);

in deinem Beispiel

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

2) Verwenden Sie eine Unterauswahl, ungefähr so:

select ...
from table t
join table pt on pt.id in (select i.id from table i where is_related(t.id,i.id));

-1

Ich habe eine Anfrage für Sie gestellt. Dadurch erhalten Sie eine rekursive Kategorie mit einer einzelnen Abfrage:

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

Hier ist eine Geige .


Bitte löschen / bearbeiten Sie Ihre Antwort, um Ihren positiven Ruf zurückzugewinnen.
fWd82
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.