2009-04-20 20 views
5

Digamos que tengo una tabla de Product, Category y Product_To_Category. Un producto puede estar en múltiples categorías.SQL para unir una tabla a otra tabla varias veces? (Asignación de productos a categorías)

 
    Product      Category  Product_to_category 
    ID | NAME    ID | Name  Prod_id | Cat_id 
    =====================  ============ =================== 
     1| Rose     1| Flowers   1| 1 
     2| Chocolate Bar   2| Food    2| 2 
     3| Chocolate Flower       3| 1 
                 3| 2 

me gustaría una consulta SQL que me da un resultado como

 
    ProductName  | Category_1 | Category_2 | Category_3 
    ======================================================= 
    Rose    | Flowers |   | 
    Chocolate Flower | Flowers | Food  | 

etc.

La mejor manera que he sido capaz de conseguir esto es la unión de un grupo de consultas juntas; una consulta para cada cantidad esperada de categorías para un producto determinado.

select p.name, cat1.name, cat2.name 
from 
    product p, 
    (select * from category c, producttocategory pc where pc.category_id = c.id) cat1, 
    (select * from category c, producttocategory pc where pc.category_id = c.id) cat2 
where p.id = cat1.id 
    and p.id = cat2.id 
    and cat1.id != cat2.id 
union all 
select p.name, cat1.name, null 
from 
    product p, 
    (select * from category c, producttocategory pc where pc.category_id = c.id) cat1 
where p.id = cat1.id 
    and not exists (select 1 from producttocategory pc where pc.product_id = p.id and pc.category_id != cat1.id) 

Existen varios problemas con esto.

  • Primero, tengo que repetir esta unión para cada categoría esperada; si un producto puede estar en 8 categorías necesitaría 8 consultas.
  • En segundo lugar, las categorías no se colocan uniformemente en las mismas columnas. Por ejemplo, a veces un producto puede tener "Comida, flores" y otro tiempo "Flores, comida".

¿Alguien sabe de una mejor manera de hacer esto? Además, ¿esta técnica tiene un nombre técnico?

Respuesta

8

No sé lo que RDBMS que está utilizando, pero en MySQL puede utilizar GROUP_CONCAT:

SELECT 
    p.name, 
    GROUP_CONCAT(c.name SEPARATOR ', ') AS categories 
FROM 
    product p 
    JOIN product_to_category pc ON p.id = pc.product_id 
    JOIN category c ON c.id = pc.category_id 
GROUP BY 
    p.name 
ORDER BY 
    p.name, 
    c.name 
+2

Creo que también necesita agregar GROUP BY, ¿verdad? – meleyal

+0

¡Absolutamente! Olvidado sobre eso, gracias! – Seb

+0

Sin GROUP BY esta consulta solo devolverá una fila, del mismo modo que una consulta COUNT (*) solo devolverá una fila. – chim

1

No es posible crear estos resultados con una consulta SQL estricto. Lo que está tratando de producir se llama una tabla dinámica . Muchas herramientas de informes admiten este tipo de comportamiento, donde debe seleccionar su producto y categoría, y luego convertir la categoría en la columna pivote.

Creo que SQL Server Analysis Services admite una funcionalidad como esta, también, pero no tengo ninguna experiencia con SSAS.

1
SELECT p.name, cat_food.name, cat_flowers.name 
FROM 
    product p 
    left outer join Product_to_category pc_food 
    on p.id = pc_food.Prod_id 
    left outer join Category cat_food 
    on pc_food.Cat_id = cat_food.id 
    AND cat_food.name = 'Food' 
    left outer join Product_to_category pc_flowers 
    on p.id = pc_flowers.Prod_id 
    left outer join Category cat_flowers 
    on pc_flowers.Cat_id = cat_flowers.id 
    AND cat_flowers.Name = 'Flowers' 

Solo funciona si conoce el número de categorías posibles, para ponerlas en columnas. Así es como funciona el SQL (estándar), el número de columnas no es dinámico.

1

La respuesta de Seb me puso en el camino correcto para una solución alternativa. Estoy usando Oracle y tiene funciones que emulan group_concat de MYSQL. Aquí hay un ejemplo. Esto no genera columnas, y por lo tanto no es tan bueno como una solución SQL pura, pero es adecuado para mis propósitos actuales.

with data as 
( 
    select 
    pc.id cat, 
    p.id prod, 
    row_number() over(partition by p.id order by pc.id) rn, 
    count(*) over (partition by p.id) cnt 
    from product_to_category pc, product p 
    where pc.product_id = p.id 
) 
select prod, ltrim(sys_connect_by_path(cat, ','), ',') cats 
    from data 
where rn = cnt 
start with rn = 1 connect by prior prod = prod and prior rn = rn - 1 
order by prod 

Esto genera datos tales como

 
PROD | CATS 
=========== 
284 | 12 
285 | 12 
286 | 9,12 

puedo editar la columna de la ltrim (sys_connect_by_path()) según sea necesario para generar todos los datos que necesito.

Cuestiones relacionadas