sql >> Base de Datos >  >> RDS >> Mysql

Clasificación de un subárbol en una estructura de datos jerárquicos de tabla de cierre

Esta pregunta surge con frecuencia no solo para la tabla de cierre sino también para otros métodos de almacenamiento de datos jerárquicos. No es fácil en ninguno de los diseños.

La solución que se me ocurrió para Closure Table implica una unión adicional. Cada nodo del árbol se une a la cadena de sus ancestros, como una consulta tipo "migas de pan". Luego use GROUP_CONCAT() para colapsar las migas de pan en una cadena separada por comas, ordenando los números de identificación por profundidad en el árbol. Ahora tienes una cadena por la que puedes ordenar.

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

Advertencias:

  • Los valores de identificación deben tener una longitud uniforme, porque ordenar "1,3" y "1,6" y "1,327" podría no dar el orden deseado. Pero ordenar "001,003" y "001,006" y "001,327" sí lo haría. Entonces, debe comenzar sus valores de identificación en 1000000+, o usar ZEROFILL para ascendiente y descendiente en la tabla category_closure.
  • En esta solución, el orden de visualización depende del orden numérico de los identificadores de categoría. Es posible que ese orden numérico de valores de identificación no represente el orden en el que desea mostrar el árbol. O puede querer la libertad de cambiar el orden de visualización independientemente de los valores de identificación numéricos. O puede que desee que aparezcan los mismos datos de categoría en más de un árbol, cada uno con un orden de visualización diferente.
    Si necesita más libertad, debe almacenar los valores de orden de clasificación por separado de los ID, y la solución se obtiene aún más complejo. Pero en la mayoría de los proyectos, es aceptable usar un atajo, dando a la identificación de categoría una función doble como orden de visualización del árbol.

Re tu comentario:

Sí, puede almacenar el "orden de clasificación de hermanos" como otra columna en la tabla de cierre y luego usar ese valor en lugar de ancestor para construir la cadena de migas de pan. Pero si hace eso, terminará con mucha redundancia de datos. Es decir, un ancestro determinado se almacena en varias filas, una para cada camino que desciende de él. Por lo tanto, debe almacenar el mismo valor para el orden de clasificación de hermanos en todas esas filas, lo que crea el riesgo de una anomalía.

La alternativa sería crear otra tabla, con solo una fila por ancestro distinto en el árbol, y únase a esa tabla para obtener el orden de hermanos.

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