Essa pergunta surge com frequência não apenas para o Closure Table, mas também para outros métodos de armazenamento de dados hierárquicos. Não é fácil em nenhum dos projetos.
A solução que encontrei para o Closure Table envolve uma junção adicional. Cada nó na árvore se junta à cadeia de seus ancestrais, como uma consulta do tipo "migalhas de pão". Em seguida, use GROUP_CONCAT() para recolher os breadcrumbs em uma string separada por vírgulas, classificando os números de id por profundidade na árvore. Agora você tem uma string pela qual você pode classificar.
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 |
+----+------------+--------+---------+-------------+
Ressalvas:
- Os valores de id devem ter comprimento uniforme, porque classificar "1,3" e "1,6" e "1,327" pode não fornecer a ordem desejada. Mas classificar "001.003" e "001.006" e "001.327" faria. Então você precisa iniciar seus valores de id em 1000000+, ou então usar
ZEROFILL
para ancestral e descendente na tabela category_closure. - Nesta solução, a ordem de exibição depende da ordem numérica dos IDs de categoria. Essa ordem numérica de valores de id pode não representar a ordem em que você deseja exibir a árvore. Ou você pode querer a liberdade de alterar a ordem de exibição independentemente dos valores numéricos do id. Ou você pode querer que os mesmos dados de categoria apareçam em mais de uma árvore, cada uma com uma ordem de exibição diferente.
Se você precisar de mais liberdade, precisará armazenar os valores de ordem de classificação separadamente dos ids, e a solução obtém ainda mais complexo. Mas, na maioria dos projetos, é aceitável usar um atalho, dando a função dupla do ID da categoria como a ordem de exibição da árvore.
Re seu comentário:
Sim, você pode armazenar "ordem de classificação de irmãos" como outra coluna na tabela de fechamento e usar esse valor em vez de
ancestor
para construir a string breadcrumbs. Mas se você fizer isso, acabará com muita redundância de dados. Ou seja, um determinado ancestral é armazenado em várias linhas, uma para cada caminho descendente dele. Portanto, você precisa armazenar o mesmo valor para a ordem de classificação dos irmãos em todas essas linhas, o que cria o risco de uma anomalia. A alternativa seria criar outra tabela, com apenas uma linha por ancestral distinto na árvore e junte-se a essa tabela para obter a ordem dos irmãos.
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 |
+----+------------+--------+---------+-------------+