Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Sortieren eines Unterbaums in einer hierarchischen Datenstruktur einer Abschlusstabelle

Diese Frage stellt sich häufig nicht nur für Closure Table, sondern auch für andere Methoden zur Speicherung hierarchischer Daten. Es ist in keinem der Designs einfach.

Die Lösung, die ich mir für Closure Table ausgedacht habe, beinhaltet einen zusätzlichen Join. Jeder Knoten im Baum verbindet sich mit der Kette seiner Vorfahren, wie eine Abfrage vom Typ "Breadcrumbs". Verwenden Sie dann GROUP_CONCAT(), um die Breadcrumbs in eine durch Kommas getrennte Zeichenfolge zu reduzieren und die ID-Nummern nach Tiefe im Baum zu sortieren. Jetzt haben Sie eine Zeichenfolge, nach der Sie sortieren können.

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

Vorbehalte:

  • Die ID-Werte sollten eine einheitliche Länge haben, da das Sortieren von "1,3" und "1,6" und "1,327" möglicherweise nicht die gewünschte Reihenfolge ergibt. Aber das Sortieren von "001.003" und "001.006" und "001.327" würde. Sie müssen also entweder Ihre ID-Werte bei 1000000+ beginnen oder ZEROFILL verwenden für Vorfahren und Nachkommen in der category_closure-Tabelle.
  • Bei dieser Lösung hängt die Anzeigereihenfolge von der numerischen Reihenfolge der Kategorie-IDs ab. Diese numerische Reihenfolge der ID-Werte entspricht möglicherweise nicht der Reihenfolge, in der Sie den Baum anzeigen möchten. Oder Sie möchten die Anzeigereihenfolge unabhängig von den numerischen ID-Werten ändern. Oder Sie möchten vielleicht, dass die gleichen Kategoriedaten in mehr als einem Baum erscheinen, jeder mit unterschiedlicher Anzeigereihenfolge.
    Wenn Sie mehr Freiheit brauchen, müssen Sie die Sortierreihenfolgewerte getrennt von den IDs speichern, und die Lösung bekommt noch komplexer. In den meisten Projekten ist es jedoch akzeptabel, eine Abkürzung zu verwenden, die der Kategorie-ID die doppelte Funktion als Baumanzeigereihenfolge gibt.

Zu Ihrem Kommentar:

Ja, Sie könnten "Geschwister-Sortierreihenfolge" als weitere Spalte in der Abschlusstabelle speichern und dann diesen Wert anstelle von ancestor verwenden um die Breadcrumbs-String zu bauen. Aber wenn Sie das tun, haben Sie am Ende eine Menge Datenredundanz. Das heißt, ein bestimmter Vorfahre wird in mehreren Zeilen gespeichert, eine für jeden davon absteigenden Pfad. Sie müssen also in all diesen Zeilen denselben Wert für die Sortierreihenfolge von Geschwistern speichern, wodurch das Risiko einer Anomalie entsteht.

Die Alternative wäre, eine weitere Tabelle mit nur einer zu erstellen Zeile pro eindeutigem Vorfahren im Baum, und verknüpfen Sie sie mit dieser Tabelle, um die Geschwisterreihenfolge zu erhalten.

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