Dette spørgsmål dukker ofte op, ikke kun for lukketabel, men også for andre metoder til lagring af hierarkiske data. Det er ikke let i nogen af designerne.
Den løsning, jeg har fundet på til Closure Table, involverer en ekstra joinforbindelse. Hver knude i træet slutter sig til kæden af sine forfædre, som en forespørgsel af typen "brødkrummer". Brug derefter GROUP_CONCAT() til at skjule brødkrummerne til en kommasepareret streng, og sorter id-numrene efter dybde i træet. Nu har du en streng, som du kan sortere efter.
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 |
+----+------------+--------+---------+-------------+
Forbehold:
- Id-værdierne skal have ensartet længde, fordi sortering af "1,3" og "1,6" og "1.327" muligvis ikke giver den rækkefølge, du har tænkt dig. Men at sortere "001.003" og "001.006" og "001.327" ville. Så du skal enten starte dine id-værdier ved 1000000+, eller også bruge
ZEROFILL
for forfader og efterkommer i kategori_afslutningstabellen. - I denne løsning afhænger visningsrækkefølgen af den numeriske rækkefølge af kategori-id'er. Den numeriske rækkefølge af id-værdier repræsenterer muligvis ikke den rækkefølge, du ønsker at vise træet. Eller du ønsker måske friheden til at ændre visningsrækkefølgen uanset de numeriske id-værdier. Eller du ønsker måske, at de samme kategoridata skal vises i mere end ét træ, hver med forskellig visningsrækkefølge.
Hvis du har brug for mere frihed, skal du gemme sorteringsrækkefølgeværdierne adskilt fra id'erne, og løsningen får endnu mere kompleks. Men i de fleste projekter er det acceptabelt at bruge en genvej, hvilket giver kategori-id'et dobbelt funktion som trævisningsrækkefølgen.
Om din kommentar:
Ja, du kan gemme "søskendesorteringsrækkefølge" som en anden kolonne i lukketabellen og derefter bruge denne værdi i stedet for ancestor
at bygge brødkrummestrengen. Men hvis du gør det, ender du med en masse dataredundans. Det vil sige, at en given forfader er gemt på flere rækker, en for hver sti, der går ned fra den. Så du skal gemme den samme værdi for søskendesorteringsrækkefølge på alle disse rækker, hvilket skaber risiko for en anomali.
Alternativet ville være at oprette en anden tabel med kun én række pr. distinkt forfader i træet, og tilføj den tabel for at få søskenderækkefølgen.
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 |
+----+------------+--------+---------+-------------+