Mysql
 sql >> Base de données >  >> RDS >> Mysql

Tri d'un sous-arbre dans une structure de données hiérarchique de table de fermeture

Cette question revient fréquemment non seulement pour Closure Table mais aussi pour d'autres méthodes de stockage de données hiérarchiques. Ce n'est pas facile dans aucun des designs.

La solution que j'ai trouvée pour Closure Table implique une jointure supplémentaire. Chaque nœud de l'arbre se joint à la chaîne de ses ancêtres, comme une requête de type "fil d'Ariane". Utilisez ensuite GROUP_CONCAT() pour réduire le fil d'Ariane dans une chaîne séparée par des virgules, en triant les numéros d'identification par profondeur dans l'arborescence. Vous avez maintenant une chaîne par laquelle vous pouvez trier.

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

Mises en garde :

  • Les valeurs d'identifiant doivent avoir une longueur uniforme, car le tri "1,3" et "1,6" et "1 327" peut ne pas donner l'ordre souhaité. Mais trier "001 003" et "001 006" et "001 327" le ferait. Vous devez donc soit commencer vos valeurs d'identifiant à 1000000+, soit utiliser ZEROFILL pour l'ancêtre et le descendant dans la table category_closure.
  • Dans cette solution, l'ordre d'affichage dépend de l'ordre numérique des identifiants de catégorie. Cet ordre numérique des valeurs d'id peut ne pas représenter l'ordre dans lequel vous souhaitez afficher l'arborescence. Ou vous pouvez avoir la liberté de modifier l'ordre d'affichage indépendamment des valeurs numériques de l'identifiant. Ou vous pouvez souhaiter que les mêmes données de catégorie apparaissent dans plusieurs arborescences, chacune avec un ordre d'affichage différent.
    Si vous avez besoin de plus de liberté, vous devez stocker les valeurs d'ordre de tri séparément des identifiants, et la solution obtient encore plus complexe. Mais dans la plupart des projets, il est acceptable d'utiliser un raccourci, donnant la double fonction de l'identifiant de catégorie en tant qu'ordre d'affichage de l'arborescence.

Concernant votre commentaire :

Oui, vous pouvez stocker "l'ordre de tri des frères et sœurs" dans une autre colonne de la table de fermeture, puis utiliser cette valeur au lieu de ancestor pour construire la chaîne de fil d'Ariane. Mais si vous faites cela, vous vous retrouvez avec beaucoup de redondance de données. C'est-à-dire qu'un ancêtre donné est stocké sur plusieurs lignes, une pour chaque chemin qui en descend. Vous devez donc stocker la même valeur pour l'ordre de tri des frères et sœurs sur toutes ces lignes, ce qui crée un risque d'anomalie.

L'alternative serait de créer une autre table, avec seulement une ligne par ancêtre distinct dans l'arborescence, et joignez-vous à cette table pour obtenir l'ordre des frères.

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