sql - tabla - ejemplos de consultas en la base de datos northwind
Seleccione productos donde la categoría pertenece a cualquier categoría en la jerarquía (9)
Esto debería recurrir a todas las categorías de "niños" a partir de una categoría determinada.
DECLARE @startingCatagoryId int
DECLARE @current int
SET @startingCatagoryId = 13813 -- or whatever the CatagoryId is for ''Processors''
CREATE TABLE #CatagoriesToFindChildrenFor
(CatagoryId int)
CREATE TABLE #CatagoryTree
(CatagoryId int)
INSERT INTO #CatagoriesToFindChildrenFor VALUES (@startingCatagoryId)
WHILE (SELECT count(*) FROM #CatagoriesToFindChildrenFor) > 0
BEGIN
SET @current = (SELECT TOP 1 * FROM #CatagoriesToFindChildrenFor)
INSERT INTO #CatagoriesToFindChildrenFor
SELECT ID FROM Catagory WHERE ParentCatagoryId = @current AND Deleted = 0
INSERT INTO #CatagoryTree VALUES (@current)
DELETE #CatagoriesToFindChildrenFor WHERE CatagoryId = @current
END
SELECT * FROM #CatagoryTree ORDER BY CatagoryId
DROP TABLE #CatagoriesToFindChildrenFor
DROP TABLE #CatagoryTree
Tengo una tabla de productos que contiene un FK para una categoría, la tabla Categorías se crea de forma que cada categoría puede tener una categoría principal, por ejemplo:
Computers
Processors
Intel
Pentium
Core 2 Duo
AMD
Athlon
Necesito hacer una consulta selectiva de que si la categoría seleccionada es Procesadores, devolverá productos que están en Intel, Pentium, Core 2 Duo, Amd, etc.
Pensé en crear algún tipo de "caché" que almacenará todas las categorías en la jerarquía para cada categoría en el archivo db e incluir el "IN" en la cláusula where. ¿Es esta la mejor solución?
Parece un trabajo para Common Table Expression ... algo parecido a:
with catCTE (catid, parentid)
as
(
select cat.catid, cat.catparentid from cat where cat.name = ''Processors''
UNION ALL
select cat.catid, cat.catparentid from cat inner join catCTE on cat.catparentid=catcte.catid
)
select distinct * from catCTE
Eso debería seleccionar la categoría cuyo nombre es ''Procesadores'' y cualquiera de sus descendientes, debería ser capaz de usar eso en una cláusula IN para retirar los productos.
He hecho cosas similares en el pasado, primero preguntando por los identificadores de categoría, luego preguntando por los productos "EN" esas categorías. Obtener las categorías es lo más difícil, y tienes algunas opciones:
- Si conoce el nivel de anidamiento de las categorías o puede encontrar un límite superior: cree un SELECT de aspecto horrible con muchas JOIN. Esto es rápido pero feo y debe establecer un límite en los niveles de la jerarquía.
- Si tiene un número relativamente pequeño de categorías en total, indague todas (solo ids, padres), recopile los identificadores de los que le interesan y haga un SELECT .... IN para los productos. Esta fue la opción adecuada para mí.
- Consulta arriba / abajo la jerarquía usando una serie de SELECT. Simple, pero relativamente lento.
- Creo que las versiones recientes de SQLServer tienen soporte para consultas recursivas, pero no las he usado yo mismo.
Los procedimientos almacenados pueden ser útiles si no desea hacer esta aplicación.
Lo que quiere encontrar es el cierre transitivo de la relación de categoría "padre". Supongo que no hay ninguna limitación para la profundidad de la jerarquía de categorías, por lo que no puede formular una sola consulta SQL que encuentre todas las categorías. Lo que haría (en pseudocódigo) es esto:
categoriesSet = empty set
while new.size > 0:
new = select * from categories where parent in categoriesSet
categoriesSet = categoriesSet+new
Así que sigue preguntando por los niños hasta que no se encuentren más. Esto se comporta bien en términos de velocidad a menos que tengas una jerarquía degenerada (digamos, 1000 categorías, cada una es hija de otra), o un gran número de categorías totales. En el segundo caso, siempre puede trabajar con tablas temporales para mantener la transferencia de datos entre su aplicación y la base de datos pequeña.
Tal vez algo así como:
select *
from products
where products.category_id IN
(select c2.category_id
from categories c1 inner join categories c2 on c1.category_id = c2.parent_id
where c1.category = ''Processors''
group by c2.category_id)
[EDITAR] Si la profundidad de la categoría es mayor que uno, esto formaría su consulta más interna. Sospecho que podría diseñar un procedimiento almacenado que profundizaría en la tabla hasta que los ids devueltos por la consulta interna no tuvieran hijos, probablemente sea mejor tener un atributo que marque una categoría como un nodo terminal en la jerarquía, luego realizar la consulta externa en esos identificadores.
CREATE TABLE #categories (id INT NOT NULL, parentId INT, [name] NVARCHAR(100))
INSERT INTO #categories
SELECT 1, NULL, ''Computers''
UNION
SELECT 2, 1, ''Processors''
UNION
SELECT 3, 2, ''Intel''
UNION
SELECT 4, 2, ''AMD''
UNION
SELECT 5, 3, ''Pentium''
UNION
SELECT 6, 3, ''Core 2 Duo''
UNION
SELECT 7, 4, ''Athlon''
SELECT *
FROM #categories
DECLARE @id INT
SET @id = 2
; WITH r(id, parentid, [name]) AS (
SELECT id, parentid, [name]
FROM #categories c
WHERE id = @id
UNION ALL
SELECT c.id, c.parentid, c.[name]
FROM #categories c JOIN r ON c.parentid=r.id
)
SELECT *
FROM products
WHERE p.productd IN
(SELECT id
FROM r)
DROP TABLE #categories
La última parte del ejemplo no funciona realmente si lo está ejecutando de esta manera. Solo elimine la selección de los productos y sustitúyala con un simple SELECT * FROM r
Me gusta usar una tabla de temperatura de pila para datos jerárquicos. aquí hay un ejemplo aproximado:
-- create a categories table and fill it with 10 rows (with random parentIds)
CREATE TABLE Categories ( Id uniqueidentifier, ParentId uniqueidentifier )
GO
INSERT
INTO Categories
SELECT NEWID(),
NULL
GO
INSERT
INTO Categories
SELECT TOP(1)NEWID(),
Id
FROM Categories
ORDER BY Id
GO 9
DECLARE @lvl INT, -- holds onto the level as we move throught the hierarchy
@Id Uniqueidentifier -- the id of the current item in the stack
SET @lvl = 1
CREATE TABLE #stack (item UNIQUEIDENTIFIER, [lvl] INT)
-- we fill fill this table with the ids we want
CREATE TABLE #tmpCategories (Id UNIQUEIDENTIFIER)
-- for this example we’ll just select all the ids
-- if we want all the children of a specific parent we would include it’s id in
-- this where clause
INSERT INTO #stack SELECT Id, @lvl FROM Categories WHERE ParentId IS NULL
WHILE @lvl > 0
BEGIN -- begin 1
IF EXISTS ( SELECT * FROM #stack WHERE lvl = @lvl )
BEGIN -- begin 2
SELECT @Id = [item]
FROM #stack
WHERE lvl = @lvl
INSERT INTO #tmpCategories
SELECT @Id
DELETE FROM #stack
WHERE lvl = @lvl
AND item = @Id
INSERT INTO #stack
SELECT Id, @lvl + 1
FROM Categories
WHERE ParentId = @Id
IF @@ROWCOUNT > 0
BEGIN -- begin 3
SELECT @lvl = @lvl + 1
END -- end 3
END -- end 2
ELSE
SELECT @lvl = @lvl - 1
END -- end 1
DROP TABLE #stack
SELECT * FROM #tmpCategories
DROP TABLE #tmpCategories
DROP TABLE Categories
hay una buena explicación aquí texto del enlace
La mejor solución para esto es en la etapa de diseño de la base de datos. Su tabla de categorías debe ser un conjunto anidado . El artículo Managing Hierarchical Data en MySQL no es tan específico de MySQL (a pesar del título), y brinda una excelente descripción general de los diferentes métodos para almacenar una jerarquía en una tabla de base de datos.
Resumen ejecutivo:
Conjuntos anidados
- Las selecciones son fáciles para cualquier profundidad
- Las inserciones y eliminaciones son difíciles
Jerarquía estándar basada en parent_id
- Las selecciones se basan en uniones internas (así que ponte peludo rápido)
- Inserciones y eliminaciones son fáciles
Por lo tanto, en función de su ejemplo, si su tabla de jerarquía era un conjunto anidado, su consulta se vería así:
SELECT * FROM products
INNER JOIN categories ON categories.id = products.category_id
WHERE categories.lft > 2 and categories.rgt < 11
el 2 y el 11 son a la izquierda y a la derecha respectivamente del registro de Processors
.
Mi respuesta a otra pregunta de hace un par de días se aplica aquí ... recursividad en SQL
Hay algunos métodos en el libro que he vinculado que deberían cubrir su situación muy bien.