Mysql
 sql >> Database >  >> RDS >> Mysql

Ordinamento di un sottoalbero in una struttura di dati gerarchici di una tabella di chiusura

Questa domanda si pone frequentemente non solo per Closure Table ma anche per altri metodi di archiviazione dei dati gerarchici. Non è facile in nessuno dei design.

La soluzione che ho escogitato per Closure Table prevede un'aggiunta aggiuntiva. Ogni nodo nell'albero si unisce alla catena dei suoi antenati, come una query di tipo "breadcrumbs". Quindi usa GROUP_CONCAT() per comprimere i breadcrumb in una stringa separata da virgole, ordinando i numeri ID in base alla profondità nell'albero. Ora hai una stringa in base alla quale puoi ordinare.

SELECT c2.*, cc2.ancestor AS `_parent`,
  GROUP_CONCAT(breadcrumb.ancestor ORDER BY breadcrumb.depth DESC) AS breadcrumbs
FROM category AS c1
JOIN category_closure AS cc1 ON (cc1.ancestor = c1.id)
JOIN category AS c2 ON (cc1.descendant = c2.id)
LEFT OUTER JOIN category_closure AS cc2 ON (cc2.descendant = c2.id AND cc2.depth = 1)
JOIN category_closure AS breadcrumb ON (cc1.descendant = breadcrumb.descendant)
WHERE c1.id = 1/*__ROOT__*/ AND c1.active = 1
GROUP BY cc1.descendant
ORDER BY breadcrumbs;

+----+------------+--------+---------+-------------+
| id | name       | active | _parent | breadcrumbs |
+----+------------+--------+---------+-------------+
|  1 | Cat 1      |      1 |    NULL | 1           |
|  3 | Cat  1.1   |      1 |       1 | 1,3         |
|  4 | Cat  1.1.1 |      1 |       3 | 1,3,4       |
|  7 | Cat 1.1.2  |      1 |       3 | 1,3,7       |
|  6 | Cat 1.2    |      1 |       1 | 1,6         |
+----+------------+--------+---------+-------------+

Avvertenze:

  • I valori id dovrebbero avere una lunghezza uniforme, perché l'ordinamento "1,3" e "1,6" e "1,327" potrebbe non dare l'ordine desiderato. Ma l'ordinamento "001.003" e "001.006" e "001.327" lo farebbe. Quindi devi iniziare i tuoi valori ID da 1000000+, oppure utilizzare ZEROFILL per antenato e discendente nella tabella category_closure.
  • In questa soluzione l'ordine di visualizzazione dipende dall'ordine numerico degli ID di categoria. L'ordine numerico dei valori id potrebbe non rappresentare l'ordine in cui si desidera visualizzare l'albero. Oppure potresti volere la libertà di modificare l'ordine di visualizzazione indipendentemente dai valori dell'ID numerico. Oppure potresti voler visualizzare i dati della stessa categoria in più di un albero, ciascuno con un ordine di visualizzazione diverso.
    Se hai bisogno di più libertà, devi memorizzare i valori di ordinamento separatamente dagli ID e la soluzione ottiene ancora più complesso. Ma nella maggior parte dei progetti, è accettabile utilizzare una scorciatoia, dando il doppio dovere dell'id della categoria come ordine di visualizzazione dell'albero.

Re il tuo commento:

Sì, puoi memorizzare "ordinamento fratelli" come un'altra colonna nella tabella di chiusura, quindi utilizzare quel valore invece di ancestor per costruire il filo di pangrattato. Ma se lo fai, finisci con molta ridondanza dei dati. Cioè, un dato antenato è memorizzato su più righe, una per ogni percorso che discende da esso. Quindi devi memorizzare lo stesso valore per l'ordinamento di pari livello su tutte quelle righe, il che crea il rischio di un'anomalia.

L'alternativa sarebbe creare un'altra tabella, con solo una riga per antenato distinto nell'albero e unisciti a quella tabella per ottenere l'ordine di pari livello.

CREATE TABLE category_closure_order (
  ancestor INT PRIMARY KEY,
  sibling_order SMALLINT UNSIGNED NOT NULL DEFAULT 1
);

SELECT c2.*, cc2.ancestor AS `_parent`,
  GROUP_CONCAT(o.sibling_order ORDER BY breadcrumb.depth DESC) AS breadcrumbs
FROM category AS c1
JOIN category_closure AS cc1 ON (cc1.ancestor = c1.id)
JOIN category AS c2 ON (cc1.descendant = c2.id)
LEFT OUTER JOIN category_closure AS cc2 ON (cc2.descendant = c2.id AND cc2.depth = 1)
JOIN category_closure AS breadcrumb ON (cc1.descendant = breadcrumb.descendant)
JOIN category_closure_order AS o ON breadcrumb.ancestor = o.ancestor
WHERE c1.id = 1/*__ROOT__*/ AND c1.active = 1
GROUP BY cc1.descendant
ORDER BY breadcrumbs;

+----+------------+--------+---------+-------------+
| id | name       | active | _parent | breadcrumbs |
+----+------------+--------+---------+-------------+
|  1 | Cat 1      |      1 |    NULL | 1           |
|  3 | Cat  1.1   |      1 |       1 | 1,1         |
|  4 | Cat  1.1.1 |      1 |       3 | 1,1,1       |
|  7 | Cat 1.1.2  |      1 |       3 | 1,1,2       |
|  6 | Cat 1.2    |      1 |       1 | 1,2         |
+----+------------+--------+---------+-------------+