Dada una tabla jerárquica como esta:
CREATE TABLE [dbo].[btree]
(
id INT PRIMARY KEY
, parent_id INT REFERENCES [dbo].[btree] ([id])
, name NVARCHAR(20)
);
Me gustaría obtener toda la estructura del árbol.
Por ejemplo, usando estos datos:
INSERT INTO [btree] VALUES (1, null, '1 Root');
INSERT INTO [btree] VALUES (2, 1, '1.1 Group');
INSERT INTO [btree] VALUES (3, 1, '1.2 Group');
INSERT INTO [btree] VALUES (4, 2, '1.1.1 Group');
INSERT INTO [btree] VALUES (5, 2, '1.1.2 Group');
INSERT INTO [btree] VALUES (6, 3, '1.2.1 Group');
INSERT INTO [btree] VALUES (7, 3, '1.2.2 Group');
INSERT INTO [btree] VALUES (8, 4, '1.1.1.1 Items');
INSERT INTO [btree] VALUES (9, 4, '1.1.1.2 Items');
INSERT INTO [btree] VALUES (10, 5, '1.1.2.1 Items');
INSERT INTO [btree] VALUES (11, 5, '1.1.1.2 Items');
INSERT INTO [btree] VALUES (12, 6, '1.2.1.1 Items');
INSERT INTO [btree] VALUES (13, 6, '1.2.1.2 Items');
INSERT INTO [btree] VALUES (14, 7, '1.2.2.1 Items');
Me gustaría obtener:
+----+-----------+---------------------+
| id | parent_id | description |
+----+-----------+---------------------+
| 1 | NULL | 1 Root |
| 2 | 1 | 1.1 Group |
| 4 | 2 | 1.1.1 Group |
| 8 | 4 | 1.1.1.1 Items |
| 9 | 4 | 1.1.1.2 Items |
| 5 | 2 | 1.1.2 Group |
| 10 | 5 | 1.1.2.1 Items |
| 11 | 5 | 1.1.2.2 Items |
| 3 | 1 | 1.2 Group |
| 6 | 3 | 1.2.1 Group |
| 12 | 6 | 1.2.1.1 Items |
| 13 | 6 | 1.2.1.2 Items |
| 7 | 3 | 1.2.2 Group |
| 14 | 7 | 1.2.2.1 Items |
+----+-----------+---------------------+
Estoy buscando registros usando una consulta recursiva como esta:
;WITH tree AS
(
SELECT c1.id, c1.parent_id, c1.name, [level] = 1
FROM dbo.[btree] c1
WHERE c1.parent_id IS NULL
UNION ALL
SELECT c2.id, c2.parent_id, c2.name, [level] = tree.[level] + 1
FROM dbo.[btree] c2 INNER JOIN tree ON tree.id = c2.parent_id
)
SELECT tree.level, tree.id, parent_id, REPLICATE(' ', tree.level - 1) + tree.name AS description
FROM tree
OPTION (MAXRECURSION 0)
;
Y este es el resultado actual:
+----+-----------+---------------------+
| id | parent_id | description |
| 1 | NULL | 1 Root |
| 2 | 1 | 1.1 Group |
| 3 | 1 | 1.2 Group |
| 6 | 3 | 1.2.1 Group |
| 7 | 3 | 1.2.2 Group |
| 14 | 7 | 1.2.2.1 Items |
| 12 | 6 | 1.2.1.1 Items |
| 13 | 6 | 1.2.1.2 Items |
| 4 | 2 | 1.1.1 Group |
| 5 | 2 | 1.1.2 Group |
| 10 | 5 | 1.1.2.1 Items |
| 11 | 5 | 1.1.1.2 Items |
| 8 | 4 | 1.1.1.1 Items |
| 9 | 4 | 1.1.1.2 Items |
+----+-----------+---------------------+
No puedo entender cómo ordenarlo por niveles.
¿Hay alguna manera de establecer un rango para cada subnivel?
He establecido un Rextester
t-sql
sql-server-2014
hierarchy
McNets
fuente
fuente
c2.id
reemplazado por un número de fila y rellenado a la izquierda para que todas las partes tengan la misma longitud. De lo contrario, no funcionará para todos los datos. Simplemente reemplace 2 con 55 en los datos y el orden cambiaPath
con una pequeña corrección, para agregar relleno.Hacer trampa, solo un poco;) ¡Mira mamá, no hay recurrencia!
Probado en rextester.com
Por supuesto, lo anterior es bastante limitado. Funciona solo bajo los supuestos:
name
columna ha almacenado (en la primera parte) la "ruta" real.CAST .. AS int
se necesita sólo si las partes son números.Explicación: El código funciona utilizando la función
PARSENAME()
que tiene el propósito principal de dividir un nombre de objeto en sus 4 partes:Tenga en cuenta que el orden es inverso. Como ejemplo,
PARSENAME('dbo.btree', 2)
nos dará'dbo'
como resultado. Con 3, obtendremos NULL (es por eso queREVERSE()
se usa dos veces en el código. De lo contrario, obtendríamos los valores nulos al principio. Se'1.2'
analizaríannull, null, 1, 2
mientras queramos)1, 2, null, null
. )Conclusión: después de todo eso, debo agregar que la respuesta de Bob Campbel es el camino a seguir, ya que es más general y produce (en la columna "ruta" en el resultado) la jerarquía de ruta, que luego puede usarse para el
ORDER BY
.Otras opciones que puede considerar, si el tamaño de la tabla crece y la solución recursiva se vuelve lenta, es almacenar la ruta en una columna separada (en un formato que sea bueno para ordenar, es decir, con relleno) o usar el proporcionado
HierarchyID
tipo que es exactamente para este caso de uso, datos jerárquicos.fuente
name
no se puede utilizar en este caso. Me llevará toda la noche descifrarlo, ¿podría tener alguna explicación?name
almacene una ruta (con texto), por ejemplo'order173.palletA27.box9'.bag3A
, aún podría usar el código (simplemente elimine los moldes a int). En cualquier caso, la consulta de BenCambell es el camino a seguir en general.