2015-11-03 16 views
19

Sto per dare 100 punti di taglie per risposta a questa domandaottenere tutti gli elementi di categoria e del suo bambino

Così ho molto difficile domanda su ricorsioni - come ottenere tutte le voci contano di categoria e tutti i childs che contiene quel genitore e più profondo fino alla fine?

Ho tavolo:

+----+---------------+-----------------+ 
| id | category name | category_parent | 
+----+---------------+-----------------+ 
| 1 | cars   |  0  | 
+----+---------------+-----------------+ 
| 2 | real estate |  0  | 
+----+---------------+-----------------+ 
| 3 | clothes  |  0  | 
+----+---------------+-----------------+ 
| 4 | bmw   |  1  | 
+----+---------------+-----------------+ 
| 5 | audi   |  1  | 
+----+---------------+-----------------+ 
| 6 | 100   |  5  | 
+----+---------------+-----------------+ 
| 7 | 80   |  5  | 
+----+---------------+-----------------+ 
| 8 | A4   |  5  | 
+----+---------------+-----------------+ 
| 9 | QUATRO  |  8  | 
+----+---------------+-----------------+ 
| 10 | TDI   |  8  | 
+----+---------------+-----------------+ 
| 11 | Black   |  9  | 
+----+---------------+-----------------+ 
| 12 | White   |  9  | 
+----+---------------+-----------------+ 
| 13 | 2 doors  |  11  | 
+----+---------------+-----------------+ 
| 14 | 5 doors  |  11  | 
+----+---------------+-----------------+ 

ed i miei prodotti tabella simile a questa:

+----+---------------+-----------------+ 
| id | category_id | name   | 
+----+---------------+-----------------+ 

e, per esempio io voglio contare tutti gli elementi che si trovano in cars categoria. Quindi in pratica dovrei passare questa categoria id (1) e in qualche modo fare una ricorsione per contare tutti gli oggetti. Ma non ho idea di come affrontarlo, perché i bambini di quella categoria potrebbero essere illimitati.

Così, quando voglio conoscere tutti gli elementi di quel conte genitore, dovrei fare qualcosa di simile:

1++: 
    4++ 
    5++: 
    6++ 
    7++ 
    8++: 
     9++: 
     11++: 
      13++ 
      14++ 
     12++ 
     10++ 

Spero che capirete quello che mi serve e mi darà qualsiasi suggerimento che mi potrebbe aiutare.

inoltre, questo è l'inizio che ho fatto fino ad ora - potrei implementarlo ma in futuro resterò bloccato in ricorsione ... quindi non vale niente.

public function get_category_tree_id_list($cat_id, $list_array = FALSE) 
{ 
    if (!$list_array){ 
     $items = $this->system->_getCustomTableData('categories', array(array('category_parent' => $cat_id)), 'id DESC'); 
     $this->__tmp['id_list'] = []; 
     foreach ($items as $key => $value) { 
      $this->__tmp['id_list'][] = $value['id']; 
     } 
    }   
} 
+0

http://stackoverflow.com/questions/5905236/parent-child-relationships-php-mysql – Stoleg

risposta

15

Molto probabilmente vorrai creare set annidati. Sono un po 'complicati da configurare, ma rendono le domande MOLTO più semplici. Quindi, invece di parent parent, avrai due colonne: lft e rgt. La sinistra e la destra sono fondamentalmente i limiti di una categoria, se l'ID della categoria di un articolo si trova tra questi valori, sai che è figlio di quella categoria.

+----+---------------+-----+------+ 
| id | category name | lft | rgt | 
+----+---------------+-----+------+ 
| 1 | cars   | 1 | 24 | 
+----+---------------+-----+------+ 
| 2 | bmw   | 2 | 3 | 
+----+---------------+-----+------+ 
| 5 | audi   | 4 | 23 | 
+----+---------------+-----+------+ 
| 6 | 100   | 5 | 6 | 
+----+---------------+-----+------+ 
| 7 | 80   | 7 | 8 | 
+----+---------------+-----+------+ 
| 8 | A4   | 9 | 22 | 
+----+---------------+-----+------+ 
| 9 | TDI   | 10 | 11 | 
+----+---------------+-----+------+ 
| 10 | Quatro  | 12 | 21 | 
+----+---------------+-----+------+ 
| 11 | Black   | 13 | 18 | 
+----+---------------+-----+------+ 
| 12 | White   | 19 | 20 | 
+----+---------------+-----+------+ 
| 13 | 2 doors  | 14 | 15 | 
+----+---------------+-----+------+ 
| 14 | 5 doors  | 16 | 17 | 
+----+---------------+-----+------+ 

Quindi, per ottenere il numero di elementi nella categoria auto, si può fare eccellente semplicemente in questo modo:

SELECT categories.name, items.id, items.category_id, items.name 
FROM categories 
LEFT JOIN items 
    ON (items.category_id BETWEEN categories.lft AND categories.rgt) 
WHERE categories.category_name = 'cars' 

Ovviamente si può semplicemente modificare il valore di category_name e ottenere gli elementi in QUALSIASI categoria.

Siamo spiacenti, per qualche motivo l'immagine è stata ruotata quando l'ho caricata qui, ma se si disegna le categorie come cerchi e quindi si numerano le linee, è possibile vedere quale dovrebbe essere il valore per sinistra e destra.

Ho fatto solo macchine da quando ho capito che potresti estrapolare per ottenere le altre categorie.

enter image description here

Quindi, se si scrive le vostre categorie in questo modo:

Cars(BMW(), Audi(100(),80(),A4(TDI(),Quatro(Black(2dr(),5dr()), White()))) 

Quindi è possibile etichettare la tua parentesi con i numeri:

Cars[1]->(BMW[2]->()<-[3], Audi[4]->(100[5]->()<-[6],80[7]->()<-[8],A4[9]->(TDI[10]->()<-[11],Quatro[12]->(Black[13]->(2dr[14]->()<-[15], 5dr[16]->()<-[17])<-[18], White[19]->()<-[20])<-[21])<-[22])<-[23])<-[24] 

O se tracciare fuori come un albero, puoi etichettarlo in questo modo, dove etichetti il ​​nodo più a sinistra con un numero e contrassegni solo il nodo giusto quando hai etichettato tutti i suoi figli:

enter image description here

+0

Penso che questo sia utile, ma potresti spiegarmi cosa sono 'lft' e' rgt'? perché non riesco a vedere l'amicizia di quelli –

+0

@ RandomSeed che non è nemmeno un duplicato di questa domanda. La domanda a cui ti sei collegato è "come faccio a strutturare i miei dati come un albero", la domanda posta qui è "come posso contare tutti i figli di una categoria, comprese le sottocategorie". La risposta a questa domanda è "tu strutturi i tuoi dati come un albero, e quindi puoi contare come questo". La risposta che hai fornito non spiega come contare i bambini. – dave

3

Si consiglia di consultare questo articolo sui suggerimenti per la gestione dei dati strutturati ad albero in MySQL.

http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql

Quale approccio si sceglie di prendere sarebbe probabilmente dipenderà da ciò che la vostra applicazione leggere e scrivere i casi d'uso sono costituiti da.

Attualmente, si sta utilizzando un modello di elenco di adiacenza, che è particolarmente problematico quando si lavora con un tipo di query arbitrario di profondità. Se questo è un caso d'uso principale per te, potresti prendere in considerazione l'approccio di un insieme annidato, che sebbene probabilmente non così intuitivo, è molto più adatto per interrogazioni arbitrarie di alberi di profondità.

La sfida con l'approccio del set nidificato è che gli aggiornamenti della tabella sono in genere più difficili da gestire.

+0

Grazie per il suggerimento informativo - Sto cercando qualcosa come ottenere l'ID di genitore e suo figlio dal modello di categorie. Una volta che lo avrò, chiamerò la ricorsione per contare gli elementi in ogni categoria e poi lo sommerò. Ma la domanda è: questo non dovrebbe mangiare troppo al mio server? –

+0

@ ArnasPečelis L'interrogazione ricorsiva è sicuramente un anti-modello che si è giustamente preoccupato di impiegare. Sulla base del tuo caso d'uso di esempio, prenderemo seriamente in considerazione l'utilizzo di un modello di serie annidato, poiché sembra che tu abbia un albero che può raggiungere una profondità arbitraria. Dai un'occhiata al esempio "Aggregate Functions in a Nested Set" da quel link per un approccio al tuo problema. –

1

di chiamare questo, è sufficiente costruire l'oggetto targetId come argomento:

come:

$counter = new RecursiveCounter(8); 
$count = $counter->getCount(); 

La Classe:

class RecursiveCounter { 
private $row; 
private $targetId; 

public function __construct($targetId) { 
    //Just setting up the info I need. You need to consider how to get the data from database and replace the constructor 
    $this->row = array(
     1 => array("category" => "cars", "parent" => 0), 
     2 => array("category" => "realestate", "parent" => 0), 
     3 => array("category" => "clothes", "parent" => 0), 
     4 => array("category" => "bmw", "parent" => 1), 
     5 => array("category" => "audi", "parent" => 1), 
     6 => array("category" => "100", "parent" => 5), 
     7 => array("category" => "80", "parent" => 5), 
     8 => array("category" => "A4", "parent" => 5), 
     9 => array("category" => "QUATRO", "parent" => 8), 
     10 => array("category" => "TDI", "parent" => 8), 
     11 => array("category" => "Black", "parent" => 9), 
     12 => array("category" => "White", "parent" => 9), 
     13 => array("category" => "doors", "parent" => 11), 
     14 => array("category" => "doors", "parent" => 11) 
    ); 
    $this->targetId = $targetId; 
} 

public function getCount() { 
    // Entry point 
    $count = 0; 
    foreach ($this->row as $id => $row) { 
     if ($this->isMatchTarget($id)) { 
      $count++; 
     } 
    } 
    return $count; 
} 
private function getParent($id) { 
    $parentId = $this->row[$id]["parent"]; 
    if (array_key_exists($parentId, $this->row)) { 
     return $parentId; 
    } else { 
     return false; 
    } 
} 

private function isMatchTarget($id) { 
    // 1. If the supplied id is the target id, job is done and return true; 
    // 2. If not: 
    //  Get the parent ud; 
    //  If parent id is not 0 (Meaning it has a parent), keep on checking 
    //   What to check? Check if the parent id is matching 
    //   If the the parent id is still not equal to target or 0, it will check the parent of parent until it they are equal or it is 0 
    //  if there is no parent, and the id dont match (Ending condidtion) 
    //   return false; 
    if ($id == $this->targetId) { 
     return true; 
    } else { 
     $parentId = $this->getParent($id); 
     if (0 != $parentId) { 
      return $this->isMatchTarget($parentId); 
     } else { 
      return false; 
     } 
    } 
} 

}

1

È possibile utilizzare la funzione ricorsiva in php per ottenere i numeri di prodotto in una categoria e la sua bambina

public function get_number_of_products_in_category($cat_id) 
{ 
    $qty = 0; 

    //get number of product in this category 
    $nb_products = $this->system->_getRowsCount('products', array(array('category_id' => $cat_id))); 
    $qty += $nb_products; 

    //get all child categories 
    $items = $this->system->_getCustomTableData('categories', array(array('category_parent' => $cat_id)), 'id DESC'); 

    //add number of products in the child category 
    if(!empty($items)) { 
     foreach ($items as $key => $value) { 
      $qty += get_number_of_products_in_category($value['id']); 
     } 
    } 

    return $qty; 
} 

per accelerare la query è necessario impostare la chiave per queste colonne:

  • category_id nei prodotti tabella
  • category_parent nella tabella categorie
1

Uso degli indici in MySQL è qualcosa che utilizza quasi nessun risorse a tutti se si utilizza correttamente gli indici. Quindi puoi contare il numero di prodotti per ogni categoria e quindi utilizzare PHP per fare il grande lavoro.

$query="SELECT c.*, count(p.*) total 
     FROM category, product p on c.id=p.category_id 
     GROUP BY c.id;"; 

Supponiamo di memorizzare il risultato della query precedente in un array $result_categories

function getMatchingItemsNb($result_categories, $category_id) { 
    $sum=0; 
    foreach ($result_categories as $row) { 
     if ($row['id']==$category_id) { 
     $sum+=$row['total']; 
     } else if ($row['category_parent']==$category_id) { 
     $sum+=getMatchingItemsNb($result_categories, $row['category_parent']); 
     } 
    } 
    return $sum; 
} 

Ciò richiede solo 1 richiesta SQL che andrà a beneficio di indici e cache. E può essere usato per ottenere più categorie totali senza interrogare mysql due volte.

Per ottenere migliori prestazioni, si può anche prendere in considerazione l'aggiunta di un campo total nella tabella category, anche se implica una certa ridondanza, ma suppongo che modifica la struttura dei dati non è oggetto qui.

4

Ho una nuova idea Penso che sarà bello. L'idea è questa: nella colonna category_parent inseriremo un riferimento a tutti i genitori di questo nodo.

 
+----+---------------+-----------------+ 
| id | category name | hierarchy | 
+----+---------------+-----------------+ 
| 1 | cars   |  1  | 
+----+---------------+-----------------+ 
| 2 | real estate |  2  | 
+----+---------------+-----------------+ 
| 3 | clothes  |  3  | 
+----+---------------+-----------------+ 
| 4 | bmw   |  1-4  | 
+----+---------------+-----------------+ 
| 5 | audi   |  1-5  | 
+----+---------------+-----------------+ 
| 6 | 100   |  1-4-6  | 
+----+---------------+-----------------+ 
| 7 | 80   |  1-4-7  | 
+----+---------------+-----------------+ 
| 8 | A4   |  1-4-8  | 
+----+---------------+-----------------+ 
| 9 | QUATRO  |  1-4-8-9  | 
+----+---------------+-----------------+ 
| 10 | TDI   |  1-4-8-10 | 
+----+---------------+-----------------+ 
| 11 | Black   | 1-4-8-9-11 | 
+----+---------------+-----------------+ 
| 12 | White   | 1-4-8-9-12 | 
+----+---------------+-----------------+ 
| 13 | 2 doors  | 1-4-8-9-11-13 | 
+----+---------------+-----------------+ 
| 14 | 5 doors  | 1-4-8-9-11-14 | 
+----+---------------+-----------------+ 

se si guarda al mio tavolo aggiornato si noterà che ogni record ha un collegamento ai propri genitori, non solo quella diretta, ma anche tutti i genitori. E per quel lavoro ho fatto qualche modifica per inserire ad essere:

Insert into table_name (category_name, hierarchy) values ('new_name', (concat(parent_hierarch, '-', (SELECT Auto_increment FROM information_schema.tables WHERE table_name='table_name')))) 

Ora lascia fare le vostre domande desiderati:

1- tutte le categorie secondarie di vetture:

select * from table_name where hierarchy like '1-%' 

2- se hai bisogno di tutti i genitori di NERO devi semplicemente digitare:

select * from table_name where hierarchy = '1-4-8-9' or hierarchy = '1-4-8' or hierarchy = '1-4' or hierarchy = '1' 

(lo puoi costruire query da php, dividendo campo Gerarchia '-' char)

3- Per vedere tutte le categorie, con il livello e la controllante diretta:

select *, SUBSTR(hierarchy, 1, (LENGTH(hierarchy) - LENGTH(id) - 1)) as parent, LENGTH(hierarchy) - LENGTH(REPLACE(hierarchy, '-', '')) as level From table_name 
 
+----+---------------+-----------------+-----------+--------+ 
| id | category name | hierarchy | parent | level | 
+----+---------------+-----------------+-----------+--------+ 
| 1 | cars   |  1  |   | 0 | 
+----+---------------+-----------------+-----------+--------+ 
| 2 | real estate |  2  |   | 0 | 
+----+---------------+-----------------+-----------+--------+ 
| 3 | clothes  |  3  |   | 0 | 
+----+---------------+-----------------+-----------+--------+ 
| 4 | bmw   |  1-4  |  1  | 1 | 
+----+---------------+-----------------+-----------+--------+ 
| 5 | audi   |  1-5  |  1  | 1 | 
+----+---------------+-----------------+-----------+--------+ 
| 6 | 100   |  1-4-6  | 1-4 | 2 | 
+----+---------------+-----------------+-----------+--------+ 
| 7 | 80   |  1-4-7  | 1-4 | 2 | 
+----+---------------+-----------------+-----------+--------+ 
| 8 | A4   |  1-4-8  | 1-4 | 2 | 
+----+---------------+-----------------+-----------+--------+ 
| 9 | QUATRO  |  1-4-8-9  | 1-4-8 | 3 | 
+----+---------------+-----------------+-----------+--------+ 
| 10 | TDI   |  1-4-8-10 | 1-4-8 | 3 | 
+----+---------------+-----------------+-----------+--------+ 
| 11 | Black   | 1-4-8-9-11 | 1-4-8-9 | 4 | 
+----+---------------+-----------------+-----------+--------+ 
| 12 | White   | 1-4-8-9-12 | 1-4-8-9 | 4 | 
+----+---------------+-----------------+-----------+--------+ 
| 13 | 2 doors  | 1-4-8-9-11-13 |1-4-8-9-11 | 5 | 
+----+---------------+-----------------+-----------+--------+ 
| 14 | 5 doors  | 1-4-8-9-11-14 |1-4-8-9-11 | 5 | 
+----+---------------+-----------------+-----------+--------+ 

Questa è una nuova idea e hanno bisogno di qualche miglioramento. Spero che tu ne tragga beneficio.

+0

questa è un'idea fantastica, grazie per la condivisione, la userò :) –

Problemi correlati