2009-07-06 17 views
9

Tengo una especie de árbol en mi base de datos MySQL.Datos jerárquicos en MySQL

Tengo una base de datos que tiene categorías, y cada categoría tiene un subcatálogo. Me quedo con todas las categorías en una tabla, por lo que las columnas son así:

*categories table* 
id | name | parent_id 
1 | Toys | 0 
2 | Dolls | 1 
3 | Bikes | 1 

Cada elemento en mi base de datos se asigna a una de esas categorías:

*items table* 
item | category_id 
barbie | 2 
schwinn| 3 

El problema es que si alguien quiere ver todos los JUGUETES (la categoría principal) ¿cuál es la mejor manera de obtener la información de la base de datos de elementos? La única forma que conozco es hacer algo como

SELECT * 
FROM items 
WHERE category_id = 2 
JOIN SELECT * 
    FROM items 
    WHERE category_id = 3 
    etc... 

Pero si tuviera como 10 categorías bajo juguetes, entonces tendría que hacer esta unión y Query 10 veces.

¿Hay una mejor manera de manejar esto?

+0

Los une son sintaxis SQL no válida (y si se fija la sintaxis por ejemplo, mediante paréntesis alrededor de la segunda seleccionar a obtener un conjunto de resultados vacío); Tal vez te refieres a UNIÓN? –

Respuesta

0

suponiendo que conoce el identificador de la categoría de juguetes, y nada está en la categoría de alto nivel Juguetes:

SELECT * FROM items WHERE category_id IN (SELECT id FROM categories WHERE parent_id = 1) 
+0

Y por cierto nada DEBERÍA estar en una categoría que no sea de hojas, exactamente por las mismas razones que Haahr da para "nunca subclasificar una clase concreta"; si necesita una subcategoría diferente/misc de juguetes, hágalo como un juguete de Toy, por lo que está en el mismo nivel que las otras subcategorías. –

3

Supongo que sabe cómo obtener el número de identificación y ese no es el punto de la pregunta. Además, parent_id también debería ser un FK que hace referencia a id, y usaría NULL para la capa superior, no 0.

Si sus categorías superiores tienen como máximo un nivel de subcategoría, puede usar esta consulta para obtener todos los juguetes:

SELECT * 
FROM items 
WHERE items.category_id IN (SELECT id FROM categories 
          WHERE categories.parent_id = 1 
          OR categories.id = 1); 

Si sus categorías se han anidado sub-categorías, tendrá que utilizar un procedimiento almacenado y lo llaman de forma recursiva. Pseudocódigo:

Procedure getItemsInCategory 
Input: @category_id integer 
Output: items rows 
{ 
    For each item in (SELECT * 
         FROM items 
         WHERE items.category_id = @category_id): 
     return the row; 

    For each id in (SELECT id 
        FROM categories 
        WHERE categories.parent_id = @category_id): 
     return the rows in getItemsInCategory(id); 
} 
4

Pero si tuviera como 10 categorías bajo juguetes, entonces tendría que hacer esta unión y de consulta 10 veces. ¿Hay una mejor manera de manejar esto?

Sí, hay una forma de almacenar datos llamada "nested sets". Es un poco más difícil insertar los datos, pero es sencillo seleccionar una rama completa de varios niveles con una única declaración select.

Además, Celko ha escrito a book sobre este tema, con un capítulo sobre conjuntos anidados y otros capítulos sobre otros métodos.

+0

+1 Por alguna razón nunca he visto esto antes y es excelente. El primer resultado (http://dev.mysql.com/tech-resources/articles/hierarchical-data.html) es hasta ahora la mejor explicación que he encontrado y es perfecto para un modelo jerárquico de datos. –

0

No estoy familiarizado con MySQL, pero esta es la forma en que lo haría en TSQL (SQL SERVER), tal vez trate de encontrar una forma equivalente de hacerlo en MySQL.

1) Bucle a través de todas las categorías para que los niños para el artículo específico, en este caso la categoría de id = 1

2) filtrar los elementos a la relativa a los niños en la Jerarquía CTE (expresión de tabla común) .

With Hierarchy As 
(

SELECT id, name, parent_id 
from   categories 
where  id = 1 
UNION ALL 
SELECT  child.id, child.name, child.parent_id 
from   categories child 
inner join Hierarchy parent on child.parent_id = parent.id 
) 
SELECT * FROM items 
WHERE category_id IN 
(
    Select id 
    from Hierarchy 
) 
21

¿Quieres ser dada la Identificación de los padres:

Así que asumen se le da

set @parentId = 1 /*toys*/ 

select 
    * 
from 
    Items i 
inner join Categories c on c.id = i.categoryId 
where 
    c.parentId = @parentId 

Esto le dará los elementos que desee - con un importante defecto de diseño: no manejar múltiples niveles de categorías jerárquicas.

Digamos que tenías esta tabla Categorías:

*Categories table* 
id | name | parentId 
1 | Toys | 0 
2 | Dolls | 1 
3 | Bikes | 1 
4 | Models | 2 
5 | Act.Fig.| 2 
6 | Mountain| 3 
7 | BMX  | 3 

Y Artículos:

*items table* 
item | category_id 
Barbie | 4 
GIJoe | 5 
Schwinn| 6 
Huffy | 7 

La única manera de obtener todos los elementos relevantes es hacer un auto de Ingreso:

select 
    * 
from 
    Items i 
inner join Categories c on c.id = i.categoryId 
inner join Categories c2 on c.parentId = c2.id 
where 
    c2.parentId = @parentId 

Este patrón no es escalable - ya que puede tener múltiples niveles de jerarquía.

Una forma común para hacer frente a las jerarquías es construir una tabla de "aplanado": una fila que une cada nodo a todos lo que es descendientes.

Además de una tabla de categorías, se construye una segunda tabla:

*CategoriesFlat table* The Name column is here only for readability 
id | name | parentId 
1 | Toys | 1 
----------------- 
2 | Dolls | 1 
2 | Dolls | 2 
----------------- 
4 | Models | 1 
4 | Models | 2 
4 | Models | 4 
5 | Act.Fig.| 1 
5 | Act.Fig.| 2 
5 | Act.Fig.| 5 
----------------- 
3 | Bikes | 1 
3 | Bikes | 3 
----------------- 
6 | Mountain| 1 
6 | Mountain| 3 
6 | Mountain| 6 
7 | BMX  | 1 
7 | BMX  | 3 
7 | BMX  | 7 

para que pueda escribir:

select 
    * 
from 
    Items i 
inner join CategoriesFlat c on c.id = i.categoryId 
where 
    c.parentId = @parentId 

y obtener todas las categorías y los artículos pertinentes.

Aquí hay una great slideshow about SQL anti-patterns y soluciones a los mismos. (Datos jerárquicos en SQL es un anti-patrón, pero no se desanime - que todos encontramos con éste)

+0

Buena presentación de diapositivas. – ChrisW

+0

¡Respuesta fantástica! Te hubiera votado más si pudiera. Muy buenas cosas para saber. Excelentes recursos también, gracias por compartir. – EvilChookie

0

me gustaría compartir mi idea con usted.

Limitación de Adyacencia Modelo: Siga el Managing Hierarchical Data in MySQL Como ya se ha descrito el modelo de adyacencia tiene la limitación de que usted tiene que saber el nivel antes de recuperar camino.

Utilice el modelo anidado: Pero si convierte la estructura de datos a su estructura de datos en el modelo de conjunto anidado, entonces puede seguir utilizando la auto unión para obtener el árbol.

Conversión de modelo jerárquico a modelo anidado: Ahora necesitamos un algoritmo de recorrido en árbol para indexar el modelo anidado. Esto se puede implementar en función de MySQL (En este momento la conversión necesita un poco de implementación del algoritmo: Un algoritmo de recorrido de árbol.No estoy seguro de cuál está mejor equipado).

Gracias :)

Cuestiones relacionadas