2009-06-13 16 views
15

Vorrei ottenere tutti gli ID dai bambini su un albero solo con MySQL.Come trovare tutti gli ID dei bambini in modo ricorsivo?

Ho una tabella come questa:

ID parent_id name 
1 0   cat1 
2 1   subcat1 
3 2   sub-subcat1 
4 2   sub-subcat2 
5 0   cat2 

ora sto cercando di ottenere tutti gli ID dei bambini per cat1 (2,3,4) in modo ricorsivo. C'è un modo per riuscirci?

risposta

15

Esistono due metodi di base per eseguire questa operazione: elenchi di adiacenza e elenchi annidati. Dai un'occhiata a Managing Hierarchical Data in MySQL.

Quello che hai è un elenco di adiacenza. No, non esiste un modo per afferrare in modo ricorsivo tutti i discendenti con una singola istruzione SQL. Se possibile, prendili tutti e mappali tutti in codice.

I set annidati possono fare ciò che si desidera ma io tendo ad evitarlo perché il costo dell'inserimento di un record è elevato ed è soggetto ad errori.

+0

C'è un modo per avere la ricorsione in una query SQL pianura RDBMS che lo supportano, come PostgreSQL – shesek

+0

Questa pagina di ritorno del collegamento non è disponibile – HMagdy

1

Probabilmente è possibile farlo con una procedura memorizzata, se questa è un'opzione per voi.

Altrimenti non è possibile farlo con una singola istruzione sql.

Idealmente si dovrebbe effettuare le chiamate ricorsive a piedi l'albero dal programma

+0

Sì, ma potrebbe semplicemente uccidere le prestazioni se l'albero è grande – dm76

-1

La tua domanda sembra un po 'impreciso. Perché vuoi averli, e cosa intendi per averli, "in un albero"?

Il tavolo che hai IS (il modo relazionale di rappresentare) l'albero.

Se si desidera che "in una tabella" con righe che contengono le coppie (ID 4, ParentID 0), è necessario la versione SQL ricorsiva del motore SQL per eseguire questa operazione, se tale motore lo supporta.

Non saprei specificamente su MySQL, ma la mia comprensione è che una volta pianificarono di implementare SQL ricorsivo usando la stessa sintassi di Oracle, cioè con CONNECT BY.

Se si guarda nel sommario del manuale per parole chiave come "query ricorsive" o "CONNECT BY", immagino che si dovrebbe essere in grado di trovare la risposta.

(Ci scusiamo per non essere in grado di fornire una più pronta per l'consumare risposta.)

0

Vedendo che la risposta è sostanzialmente no o almeno non molto facile con una singola istruzione MYSQL, vi posterò la mia codice php/mysql per fare la lista gerarchia ..

function createCategorySubArray() 
{ 
    $categories = getSQL("SELECT pos_category_id FROM pos_categories"); 
    for($i=0;$i<sizeof($categories);$i++) 
    { 
     //here we need to find all sub categories 
     $pos_category_id = $categories[$i]['pos_category_id']; 
     $cat_list[$pos_category_id] = recursiveCategory($pos_category_id,array()); 

    } 
    return $cat_list; 

} 
function recursiveCategory($pos_category_id, $array) 
{ 
    $return = getSql("SELECT pos_category_id FROM pos_categories WHERE parent = $pos_category_id"); 
    for($i=0;$i<sizeof($return);$i++) 
    { 
     $sub_cat = $return[$i]['pos_category_id']; 
     $array[] = $sub_cat; 
     $array = recursiveCategory($sub_cat, $array); 
    } 
    return $array; 
} 

Poi si chiamano da $ cat_array = createCategorySubArray();

Ho bisogno di questo per scoprire quali promozioni in base alle categorie di prodotti vengono applicate alle sottocategorie.

1

creare la tabella dovrebbe essere assomigliare sotto

DROP TABLE IF EXISTS `parent_child`; 
CREATE TABLE `parent_child` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `name` varchar(255) DEFAULT NULL, 
    `parent_id` int(11) DEFAULT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1; 

insert into `parent_child`(`id`,`name`,`parent_id`) 
values (1,'cat1',0),(2,'subcat1',1), 
(3,'sub-subcat1',2),(4,'sub-subcat2',2), 
(5,'cat2',0); 

Creare funzione per ottenere elemento figlio genitore

DELIMITER $$ 

USE `yourdatabase`$$ 

DROP FUNCTION IF EXISTS `GetAllNode1`$$ 

CREATE DEFINER=`root`@`localhost` FUNCTION `GetAllNode1`(GivenID INT) RETURNS TEXT CHARSET latin1 
    DETERMINISTIC 
BEGIN 
    DECLARE rv,q,queue,queue_children TEXT; 
    DECLARE queue_length,front_id,pos INT; 
    SET rv = ''; 
    SET queue = GivenID; 
    SET queue_length = 1; 
    WHILE queue_length > 0 DO 
     SET front_id = queue; 
     IF queue_length = 1 THEN 
      SET queue = ''; 
     ELSE 
      SET pos = LOCATE(',',queue) + 1; 
      SET q = SUBSTR(queue,pos); 
      SET queue = q; 
     END IF; 
     SET queue_length = queue_length - 1; 
     SELECT IFNULL(qc,'') INTO queue_children 
     FROM (SELECT GROUP_CONCAT(id) AS qc 
     FROM `parent_child` WHERE `parent_id` = front_id) A ; 
     IF LENGTH(queue_children) = 0 THEN 
      IF LENGTH(queue) = 0 THEN 
       SET queue_length = 0; 
      END IF; 
     ELSE 
      IF LENGTH(rv) = 0 THEN 
       SET rv = queue_children; 
      ELSE 
       SET rv = CONCAT(rv,',',queue_children); 
      END IF; 
      IF LENGTH(queue) = 0 THEN 
       SET queue = queue_children; 
      ELSE 
       SET queue = CONCAT(queue,',',queue_children); 
      END IF; 
      SET queue_length = LENGTH(queue) - LENGTH(REPLACE(queue,',','')) + 1; 
     END IF; 
    END WHILE; 
    RETURN rv; 
END$$ 

DELIMITER ; 

richiesta Write per l'uscita desiderio

01.235.
SELECT GetAllNode1(id) FROM parent_child 
or 
SELECT GetAllNode1(id) FROM parent_child where id =1 //for specific parent's child element 
6

Ecco un semplice singolo-query mysql-soluzione:

SELECT GROUP_CONCAT(Level SEPARATOR ',') FROM (
    SELECT @Ids := (
     SELECT GROUP_CONCAT(`ID` SEPARATOR ',') 
     FROM `table_name` 
     WHERE FIND_IN_SET(`parent_id`, @Ids) 
    ) Level 
    FROM `table_name` 
    JOIN (SELECT @Ids := <id>) temp1 
) temp2 

Basta sostituire <id> con l'elemento principale del ID.

Ciò restituirà una stringa con i ID s di tutti i discendenti dell'elemento con ID = <id>, separati da ,. Se si preferisce avere più righe restituite, con un discendente su ogni riga, si può usare qualcosa di simile:

SELECT * 
FROM `table_name` 
WHERE FIND_IN_SET(`ID`, (
    SELECT GROUP_CONCAT(Level SEPARATOR ',') FROM (
     SELECT @Ids := (
      SELECT GROUP_CONCAT(`ID` SEPARATOR ',') 
      FROM `table_name` 
      WHERE FIND_IN_SET(`parent_id`, @Ids) 
    ) Level 
     FROM `table_name` 
     JOIN (SELECT @Ids := <id>) temp1 
    ) temp2 
)) 

comprendente l'elemento root/genitore

Il PO ha chiesto per i figli di un elemento, a cui si risponde sopra. In alcuni casi potrebbe essere utile includere l'elemento root/genitore nel risultato. Qui sono le mie soluzioni suggerite:

stringa separato da virgole di ids:

SELECT GROUP_CONCAT(Level SEPARATOR ',') FROM (
    SELECT <id> Level 
    UNION 
    SELECT @Ids := (
     SELECT GROUP_CONCAT(`ID` SEPARATOR ',') 
     FROM `table_name` 
     WHERE FIND_IN_SET(`parent_id`, @Ids) 
    ) Level 
    FROM `table_name` 
    JOIN (SELECT @Ids := <id>) temp1 
) temp2 

Più righe:

SELECT * 
FROM `table_name` 
WHERE `ID` = <id> OR FIND_IN_SET(`ID`, (
    SELECT GROUP_CONCAT(Level SEPARATOR ',') FROM (
     SELECT @Ids := (
      SELECT GROUP_CONCAT(`ID` SEPARATOR ',') 
      FROM `table_name` 
      WHERE FIND_IN_SET(`parent_id`, @Ids) 
    ) Level 
     FROM `table_name` 
     JOIN (SELECT @Ids := <id>) temp1 
    ) temp2 
)) 
+0

Puoi spiegare la tua soluzione, sembra molto promettente ma non sono sicuro di averlo capito. @Ids una funzione alias/var usata in modo ricorsivo per l'iscrizione e hai più informazioni su quali versioni di mysql supportano questo tipo di query o no? – MonkeyMonkey

+0

@Ids è una [variabile definita dall'utente] (http://dev.mysql.com/doc/refman/5.7/en/user-variables.html), che è stata utilizzata in MySQL per un lungo periodo. Penso che la query dovrebbe essere eseguita in qualsiasi versione di MySQL che vorresti usare. La query funziona concatenando gli ID di tutti gli elementi che sono figli dell'elemento originale nella prima riga, quindi concatena gli ID di tutti gli elementi figli di qualsiasi elemento incluso nella prima riga nella seconda riga, ecc. fine, tutte le righe sono concatenate su una singola riga. Facoltativamente, tale riga può essere suddivisa in modo che ogni elemento ottenga la propria riga (ultima query). –

+1

Questo è grande e geniale, lo stavamo usando da un po ', ma oggi ho capito che i bambini dell'ultimo livello annidato sono troncati - ci deve essere 'OR FIND_IN_SET (id, @Ids)' nella clausola 'WHERE'. Grazie! – Wirone

Problemi correlati