2009-04-20 10 views
5

Załóżmy, że mam tabelę Product, Category i Product_To_Category. Produkt może być w wielu kategoriach.SQL, aby wielokrotnie połączyć jedną tabelę z inną tabelą? (Mapowanie produktów do kategorii)

 
    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 

Chciałbym zapytanie SQL, które daje mi wynik taki jak

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

itp

Najlepszym sposobem udało mi się dostać to do unii grono zapytania wspólnie; jedno zapytanie dla każdej spodziewanej liczby kategorii dla danego produktu.

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) 

Jest kilka problemów z tym.

  • Najpierw muszę powtórzyć ten związek dla każdej oczekiwanej kategorii; jeśli produkt może być w 8 kategoriach, potrzebuję 8 zapytań.
  • Po drugie, kategorie nie są równomiernie umieszczane w tych samych kolumnach. Na przykład czasami produkt może mieć "Jedzenie, kwiaty", a innym razem "Kwiaty, jedzenie".

Czy ktoś wie o lepszy sposób to zrobić? Czy ta technika ma również nazwę techniczną?

Odpowiedz

8

nie wiem co RDBMS używasz, ale w MySQL można użyć 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

Myślę, że musisz również dodać GROUP BY, prawda? – meleyal

+0

Absolutnie! Zapomniałem o tym, dzięki! – Seb

+0

Bez grupy GROUP PRZEZ to zapytanie zwróci tylko jeden wiersz, w taki sam sposób, w jaki zapytanie COUNT (*) zwróci tylko jeden wiersz. – chim

1

Nie można tworzyć te wyniki ze ścisłą zapytania SQL. To, co próbujesz wyprodukować, nazywa się tabelą przestawną . Wiele narzędzi do raportowania obsługuje ten rodzaj zachowania, w którym należy wybrać produkt i kategorię, a następnie zmienić kategorię w kolumnę przestawną.

Wierzę, że SQL Server Analysis Services obsługuje również takie funkcje, ale nie mam żadnego doświadczenia z 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' 

Działa tylko, jeśli znasz liczbę możliwych kategorii, aby umieścić je w kolumnach. Tak działa (standardowy) SQL, liczba kolumn nie jest dynamiczna.

1

Odpowiedź Seba postawiła mnie na dobrej drodze do obejścia problemu. Używam Oracle i ma funkcje, które emulują MySQL group_concat. Oto przykład. To nie generuje kolumn, a zatem nie jest tak dobre, jak czyste rozwiązanie SQL, ale jest odpowiednie dla moich obecnych celów.

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 

Generuje dane, takie jak

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

można zmodyfikować (sys_connect_by_path()) kolumny LTrim w razie potrzeby w celu wytworzenia, co dane potrzebne.

Powiązane problemy