Come creare una query ricorsiva gerarchica MySQL


271

Ho una tabella MySQL che è la seguente:

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

Ora, voglio avere una singola query MySQL a cui fornisco semplicemente l'id [ad esempio dire 'id = 19'], quindi dovrei ottenere tutti i suoi ID figlio [cioè il risultato dovrebbe avere ID '20, 21,22 ']. ... Inoltre, la gerarchia dei bambini non è nota, può variare ....

Inoltre, ho già la soluzione usando il ciclo for ..... Fammi sapere come ottenere lo stesso usando una singola query MySQL, se possibile.


Supponiamo che la gerarchia sia profonda 7 livelli. Come ti aspetti dalla tabella di output?
Jonathan Leffler,

1
MySQL (ancora) non supporta le query gerarchiche (come fanno altri DBMS moderni). Sarà necessario scrivere una procedura memorizzata o utilizzare un modello di dati diverso.
a_horse_with_no_name il


1
MYSQL 8.0 supporterà la query ricorsiva usando CTE (Common Table Expressions)
user3712320

Che ne dici di ottenere l'elenco completo dei post a partire dall'ID dell'ultimo commento? O l'ultimo figlio?
joe

Risposte:


393

Per MySQL 8+: utilizzare la withsintassi ricorsiva .
Per MySQL 5.x: utilizzare variabili in linea, ID percorso o self-join.

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;

Il valore specificato in parent_id = 19deve essere impostato idsu del padre di cui si desidera selezionare tutti i discendenti.

MySQL 5.x

Per le versioni di MySQL che non supportano le espressioni di tabella comuni (fino alla versione 5.7), si otterrebbe questo risultato con la seguente query:

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

Ecco un violino .

Qui, il valore specificato in @pv := '19'dovrebbe essere impostato idsu del padre di cui si desidera selezionare tutti i discendenti.

Questo funzionerà anche se un genitore ha più figli. Tuttavia, è necessario che ogni record soddisfi la condizione parent_id < id, altrimenti i risultati non saranno completi.

Assegnazioni di variabili all'interno di una query

Questa query utilizza una sintassi MySQL specifica: le variabili vengono assegnate e modificate durante la sua esecuzione. Vengono fatte alcune ipotesi sull'ordine di esecuzione:

  • La fromclausola viene valutata per prima. È qui che @pvviene inizializzato.
  • La whereclausola viene valutata per ciascun record nell'ordine di recupero dagli fromalias. Quindi è qui che viene inserita una condizione per includere solo i record per i quali il genitore è già stato identificato come albero dell'albero discendente (tutti i discendenti del genitore primario vengono progressivamente aggiunti @pv).
  • Le condizioni in questa whereclausola sono valutate in ordine e la valutazione viene interrotta una volta che il risultato totale è certo. Pertanto, la seconda condizione deve trovarsi al secondo posto, in quanto aggiunge l' idelenco padre e ciò dovrebbe verificarsi solo se idpassa la prima condizione. La lengthfunzione viene chiamata solo per assicurarsi che questa condizione sia sempre vera, anche se pvper qualche motivo la stringa restituisse un valore errato.

Tutto sommato, si potrebbero trovare queste ipotesi troppo rischiose per poter contare. La documentazione avverte:

potresti ottenere i risultati che ti aspetti, ma ciò non è garantito [...] l'ordine di valutazione per le espressioni che coinvolgono le variabili utente non è definito.

Pertanto, anche se funziona in modo coerente con la query precedente, l'ordine di valutazione può comunque cambiare, ad esempio quando si aggiungono condizioni o si utilizza questa query come vista o query secondaria in una query più grande. È una "caratteristica" che verrà rimossa in una futura versione di MySQL :

Nelle versioni precedenti di MySQL era possibile assegnare un valore a una variabile utente in istruzioni diverse da SET. Questa funzionalità è supportata in MySQL 8.0 per la compatibilità con le versioni precedenti ma è soggetta a rimozione in una versione futura di MySQL.

Come detto sopra, da MySQL 8.0 in poi dovresti usare la withsintassi ricorsiva .

Efficienza

Per set di dati molto grandi questa soluzione potrebbe rallentare, poiché l' find_in_setoperazione non è il modo più ideale per trovare un numero in un elenco, certamente non in un elenco che raggiunge una dimensione nello stesso ordine di grandezza del numero di record restituiti.

Alternativa 1: with recursive,connect by

Sempre più database implementano la sintassi standard SQL: 1999 ISOWITH [RECURSIVE] per le query ricorsive (ad esempio 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+ ). E a partire dalla versione 8.0, anche MySQL lo supporta . Vedere la parte superiore di questa risposta per la sintassi da utilizzare.

Alcuni database hanno una sintassi alternativa e non standard per le ricerche gerarchiche, come la CONNECT BYclausola disponibile su Oracle , DB2 , Informix , CUBRID e altri database.

MySQL versione 5.7 non offre tale funzionalità. Quando il tuo motore di database fornisce questa sintassi o puoi migrare a uno che lo fa, questa è sicuramente l'opzione migliore da scegliere. In caso contrario, considera anche le seguenti alternative.

Alternativa 2: identificatori stile percorso

Le cose diventano molto più semplici se si assegnassero idvalori che contengono le informazioni gerarchiche: un percorso. Ad esempio, nel tuo caso questo potrebbe apparire così:

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

Quindi il tuo selectsarebbe simile a questo:

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

Alternativa 3: ripetuti self-join

Se conosci un limite massimo per quanto può essere profondo il tuo albero gerarchico, puoi utilizzare una sqlquery standard come questa:

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;

Vedi questo violino

La wherecondizione specifica di quale genitore desideri recuperare i discendenti. È possibile estendere questa query con più livelli in base alle esigenze.


26
Mi piace la tua spiegazione. Non dà solo una risposta, spiega perché risolve il problema in modo che possiamo effettivamente imparare da esso. EDIT: è anche bello che non si basi sul conoscere il numero di livelli in anticipo.
Byson,

1
@Bison, apprezzo molto il tuo feedback. Grazie!
trincot

2
@ Avión, non è qualcosa che devi mettere da qualche parte, è un requisito che per tutti i record questa condizione è vera. Se si dispone di uno o più record in cui parent_id > idnon è possibile utilizzare questa soluzione.
trincot

2
Per chiunque cerchi di usare il WITH RECURSIVEmetodo, ho trovato il seguente articolo davvero utile con diversi scenari come profondità di ricorsione, distinzioni e cicli di rilevazione e chiusura
Cavallo

2
Ho provato la soluzione principale su MySQL5.7 sul mio computer, sui miei tavoli, ma non ha funzionato a causa dell'equivalente della clausola @pv: = concat (@pv, ',', id) viene valutato come falso. L'ho corretto modificandolo in lunghezza (@pv: = concat (@pv, ',', id))> 0, quindi è sempre vero.
KC Wong,

82

Dal blog Gestione dei dati gerarchici in MySQL

Struttura del tavolo

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

Query:

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

Produzione

+-------------+----------------------+--------------+-------+
| 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 maggior parte degli utenti, una volta o l'altra, ha trattato i dati gerarchici in un database SQL e senza dubbio ha appreso che la gestione dei dati gerarchici non è ciò a cui è destinato un database relazionale. Le tabelle di un database relazionale non sono gerarchiche (come XML), ma sono semplicemente un elenco semplice. I dati gerarchici hanno una relazione padre-figlio che non è naturalmente rappresentata in una tabella di database relazionale. Leggi di più

Consulta il blog per maggiori dettagli.

MODIFICARE:

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

Produzione:

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

Riferimento: come eseguire la query SELECT ricorsiva in Mysql?


23
Va bene purché nella gerarchia non ci siano più di 4 livelli al massimo. Se ci sono N livelli, devi sapere che per creare correttamente la query.
Jonathan Leffler,

2
@Damodaran, grazie per la tua risposta ... Ciò di cui avevo bisogno è una condizione in cui il numero di bambini non è noto ... e nel blog che utilizza un concetto di join interno in quanto è necessario conoscere la gerarchia che non lo è nel mio caso ... quindi fammi sapere la tua opinione sullo stesso ... Quindi, in parole semplici, ho bisogno di una query per gestire i livelli di 'n' hirerachy in cui 'n' non è noto .....
Tarun Parswani

1
@ user3036105: non è possibile farlo in MySQL con una singola query SQL. MySQL semplicemente non è abbastanza avanzato per quello. Se ne hai davvero bisogno, prendi in considerazione l'aggiornamento a un DBMS che supporta query ricorsive.
a_horse_with_no_name il

5
> La maggior parte degli utenti, una volta o l'altra, hanno trattato i dati gerarchici in un database SQL e senza dubbio hanno appreso che la gestione dei dati gerarchici non è la finalità di un database relazionale. Forse intendevi un database MySQL. Un database Oracle gestisce abbastanza bene i dati gerarchici e le query.
Peter Nosko,

1
"... la gestione dei dati gerarchici non è ciò a cui un database relazionale è destinato ..." Mentre questa potrebbe non essere stata l'intenzione originale di un database relazionale, nel mondo reale i dati gerarchici sono incredibilmente banali e MySQL dovrebbe riflettere come le persone devono effettivamente utilizzare i propri dati in scenari del mondo reale.
Dave L,

9

Prova questi:

Definizione della tabella:

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;

Righe sperimentali:

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

Stored procedure ricorsive:

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 ;

Funzione wrapper per la procedura memorizzata:

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 ;

Seleziona esempio:

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

Produzione:

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

Filtraggio di righe con un determinato percorso:

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

Produzione:

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

1
Questo non funzionerà per più di un bambino. es.(20, 'category2', 19), (21, 'category3', 20), (22, 'category4', 20),
Codice pulito

3
Sono abbastanza sicuro che funzioni per più di un bambino. L'ho anche provato di nuovo.
Fandi Susanto,

@Fandi Susanto, grazie mi aiuta molto.
Dogan Ozer,

9

L'approccio migliore che ho escogitato è

  1. Utilizzare la discendenza per memorizzare alberi \ sort \ trace. È più che sufficiente e funziona migliaia di volte più velocemente per la lettura rispetto a qualsiasi altro approccio. Permette anche di rimanere su quel modello anche se il DB cambierà (poiché QUALSIASI db consentirà di usare quel modello)
  2. Utilizzare la funzione che determina la discendenza per un ID specifico.
  3. Usalo come desideri (in selezioni, o su operazioni CUD, o anche per lavori).

Approccio di discendenza descr. può essere trovato ovunque, ad esempio qui o qui . Per quanto riguarda la funzione, questo è ciò che mi ha ispirato.

Alla fine, ho ottenuto una soluzione più o meno semplice, relativamente veloce e SEMPLICE.

Corpo della funzione

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

E poi solo tu

select get_lineage(the_id)

Spero che aiuti qualcuno :)


9

Ha fatto la stessa cosa per un'altra richiesta qui

Mysql select recursive get all child con livello multiplo

La query sarà:

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;

Come possiamo farlo? 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; Non posso fare riferimento a F1.idFolder per @pv
Rahul il

Ho ricreato la tabella dalla domanda originale di OP con i dati come mostrato nel loro commento, quindi ho eseguito la tua query qui e ho ottenuto un singolo NULLcome risultato. Sai perché potrebbe essere? Esistono prerequisiti in termini di motore di database o è cambiato qualcosa da quando hai creato questa risposta che rende obsoleta questa query?
Digital Ninja

7

Se hai bisogno di una velocità di lettura rapida, l'opzione migliore è utilizzare una tabella di chiusura. Una tabella di chiusura contiene una riga per ogni coppia di antenati / discendenti. Quindi, nel tuo esempio, sembrerebbe la tabella di chiusura

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

Una volta che hai questa tabella, le query gerarchiche diventano molto facili e veloci. Per ottenere tutti i discendenti della categoria 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

Certo, c'è un grande svantaggio ogni volta che usi dati denormalizzati come questo. È necessario mantenere la tabella di chiusura accanto alla tabella delle categorie. Il modo migliore è probabilmente usare i trigger, ma è in qualche modo complesso tracciare correttamente inserimenti / aggiornamenti / eliminazioni per le tabelle di chiusura. Come in ogni altra cosa, devi esaminare le tue esigenze e decidere quale approccio è meglio per te.

Modifica : vedere la domanda Quali sono le opzioni per l'archiviazione di dati gerarchici in un database relazionale? per più opzioni. Esistono diverse soluzioni ottimali per diverse situazioni.


4

Interrogazione semplice per elencare il figlio della prima ricorsione:

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

Risultato:

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

... con join sinistro:

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 soluzione di @tincot per elencare tutti i bambini:

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)

Provalo online con Sql Fiddle e vedi tutti i risultati.

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


3

Puoi farlo così in altri database abbastanza facilmente con una query ricorsiva (YMMV sulle prestazioni).

L'altro modo per farlo è quello di memorizzare due ulteriori bit di dati, un valore sinistro e destro. Il valore sinistro e destro derivano da un attraversamento del preordine della struttura ad albero che stai rappresentando.

Questo è noto come Traversal Tree Preorder modificato e consente di eseguire una query semplice per ottenere tutti i valori padre contemporaneamente. Va anche con il nome "set nidificato".


Volevo aggiungere un commento simile al tuo, ma da quando lo hai fatto, aggiungerò solo un link a un buon esempio del "set nidificato": mikehillyer.com/articles/managing-hierarchical-data-in-mysql
Miroslaw Opoka,

2

Usa semplicemente la classe php BlueM / tree per creare l'albero di una tabella di auto-relazione in mysql.

Tree e Tree \ Node sono classi PHP per la gestione di dati strutturati gerarchicamente utilizzando riferimenti di ID padre. Un esempio tipico è una tabella in un database relazionale in cui il campo "padre" di ogni record fa riferimento alla chiave primaria di un altro record. Naturalmente, Tree non può utilizzare solo i dati provenienti da un database, ma qualsiasi cosa: i dati vengono forniti e Tree li utilizza, indipendentemente da dove provengono i dati e come sono stati elaborati. leggi di più

Ecco un esempio dell'uso di 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

inserisci qui la descrizione dell'immagine

È una tabella delle categorie .

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)

Produzione:: inserisci qui la descrizione dell'immagine


1
Puoi spiegarlo? Ma garantisco che funziona. Grazie.
Wobsoriano,

1
per favore spiega la domanda e qual è il significato di @pv ?? Come funziona il loop in questa query?
Amanjot Kaur,

2
Non sembra funzionare a tutti i livelli se ci sono bambini con ID inferiore rispetto ai genitori. :(
Jonas,

1
@Jonas mi ha impiegato 20 minuti per identificare il problema reale, provando con combinazioni diverse. sì hai ragione. Non funzionerà con ID inferiore al suo ID principale. Hai qualche soluzione?
muaaz,

@muaaz Alla fine l'ho risolto utilizzando un campo "percorso" che contiene il percorso per la rispettiva riga, ad esempio la riga con ID 577 ha il percorso "/ 1/2/45/577 /". Se stai cercando tutti i figli dell'ID 2, puoi semplicemente selezionare tutte le righe con il percorso LIKE "/ 1/2 /%". L'unico aspetto negativo è che devi aggiornare i percorsi nei tuoi metodi di aggiornamento. Ma per MySQL 5.6 (compatibile), è stata l'unica soluzione che ha funzionato per me.
Jonas il

1

È un po 'complicato, controlla se funziona per te

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

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

Sostituisci con il campo e il nome della tabella in modo appropriato.


In questo caso non funzionerà sqlfiddle.com/#!2/19360/2 , con questo trucco, almeno dovresti ordinare prima a livello gerarchico.
Jaugar Chang,

1

Qualcosa che non è menzionato qui, sebbene un po 'simile alla seconda alternativa della risposta accettata ma diverso ea basso costo per la query della grande gerarchia e gli elementi facili (inserisci aggiornamento elimina), aggiungerebbe una colonna di percorso persistente per ogni elemento.

alcuni come:

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

Esempio:

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

Ottimizza la lunghezza del percorso e ORDER BY pathutilizza la codifica base36 anziché l'ID del percorso numerico reale

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

Sopprimendo anche il separatore '/' della barra utilizzando lunghezza fissa e riempimento nell'ID codificato

Spiegazione dettagliata dell'ottimizzazione qui: https://bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-materialized-path/

FARE

creazione di una funzione o procedura per dividere il percorso per recuperare antenati di un elemento


Grazie! Interessante conbase36
Vlad

0

Questo funziona per me, spero che funzionerà anche per te. Ti darà un set di record Root su Child per qualsiasi menu specifico. Modificare il nome del campo in base alle proprie esigenze.

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;

Non sembra funzionare a tutti i livelli se ci sono bambini che hanno un documento d'identità più grande dei loro genitori
muaaz,

-1

Ho trovato più facilmente:

1) creare una funzione che controllerà se un elemento si trova in qualsiasi punto della gerarchia padre di un altro. Qualcosa del genere (non scriverò la funzione, ce la farò con WHILE DO):

is_related(id, parent_id);

nel tuo esempio

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

2) usa una sottoselezione, qualcosa del genere:

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

Ho fatto una domanda per te. Questo ti darà una categoria ricorsiva con una singola query:

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

Ecco un violino .


Elimina / modifica la tua risposta per recuperare la tua reputazione positiva.
fWd82,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.