Biorąc pod uwagę następującą (bardzo uproszczoną) strukturę tabeli mysql:
produkty
- ID
Kategorie produktów
- ID
- ID produktu
- status (liczba całkowita)
znaczniki_produktu
- ID
- ID produktu
- some_other_numeric_value
Próbuję znaleźć każdy produkt, który ma powiązanie z określonym tagiem produktu, i że relacja z co najmniej jedną kategorią, której atrybut-status wynosi 1.
Próbowałem następujące kwerendy:
SELECT *
FROM `product` p
JOIN `product_categories` pc
ON p.`product_id` = pc.`product_id`
JOIN `product_tags` pt
ON p.`product_id` = pt.`product_id`
WHERE pt.`some_value` = "some comparison value"
GROUP BY p.`product_id`
HAVING SUM( pc.`status` ) > 0
ORDER BY SUM( pt.`some_other_numeric_value` ) DESC
Teraz moim problemem jest: SUM(pt.some_other_numeric_value)
zwraca nieoczekiwane wartości.
Uświadomiłem sobie, że jeśli dany produkt ma więcej niż jeden związek z Kategorie produktów tabela, a następnie każda relacja do znaczniki_produktu tabela jest liczona tyle razy, ile jest relacji do Kategorie produktów stół!
Na przykład: jeśli produkt o id = 1 ma związek z kategoriami produktu o ids = 2, 3 i 4 oraz relacją z tagami produktu o identyfikatorach 5 i 6 - to jeśli wstawię GROUP_CONCAT(pt.id)
, to daje 5,6,5,6,5,6 zamiast oczekiwanego 5,6.
Na początku podejrzewałem, że to problem ztyp złączenia (lewy, prawy, wewnętrzny, itd.), więc wypróbowałem każdy znany mi typ złączenia, ale bezskutecznie. Próbowałem także dołączyć do pola więcej identyfikatorów GROUP BY
klauzula, ale to też nie rozwiązało problemu.
Czy ktoś może mi wyjaśnić, co się tu właściwie dzieje?
Odpowiedzi:
5 dla odpowiedzi № 1Dołączasz do „głównego” (product
) do dwóch tabel (tags
i categories
) przez 1:n
związki, więc tego się spodziewacie, jesteścietworząc produkt mini-kartezjański. W przypadku produktów, które mają więcej niż jeden powiązany tag i więcej niż jedną powiązaną kategorię, zestaw wyników tworzy wiele wierszy. Jeśli grupujesz według, masz złe wyniki w funkcjach agregujących.
Jednym ze sposobów uniknięcia tego jest usunięcie jednego z dwóch złączeń, co jest prawidłową strategią, jeśli nie potrzebujesz wyników z tej tabeli. Powiedzmy, że nie potrzebujesz niczego w SELECT
lista z product_categories
stół. Następnie możesz użyć połączenia częściowego ( EXISTS subquery)
do tego stołu:
SELECT p.*,
SUM( pt.`some_other_numeric_value` )
FROM `product` p
JOIN `product_tags` pt
ON p.`product_id` = pt.`product_id`
WHERE pt.`some_value` = "some comparison value"
AND EXISTS
( SELECT *
FROM product_categories pc
WHERE pc.product_id = pc.product_id
AND pc.status = 1
)
GROUP BY p.`product_id`
ORDER BY SUM( pt.`some_other_numeric_value` ) DESC ;
Innym sposobem na obejście tego problemu jest - po GROUP BY MainTable.pk
- używać DISTINCT
w środku COUNT()
lub GROUP_CONCAT()
funkcje agregujące. To działa, ale nie można go używać z SUM()
. Tak więc nie jest to przydatne w Twoim konkretnym zapytaniu.
Trzecią opcją - która zawsze działa - jest najpierw zgrupowanie przy dwóch (lub więcej) stolikach bocznych, a następnie dołączenie do stołu głównego. Coś takiego w twoim przypadku:
SELECT p.* ,
COALESCE(pt.sum_other_values, 0) AS sum_other_values
COALESCE(pt.cnt, 0) AS tags_count,
COALESCE(pc.cnt, 0) AS categories_count,
COALESCE(category_titles, "") AS category_titles
FROM `product` p
JOIN
( SELECT product_id
, COUNT(*) AS cnt
, GROUP_CONCAT(title) AS category_titles
FROM `product_categories` pc
WHERE status = 1
GROUP BY product_id
) AS pc
ON p.`product_id` = pc.`product_id`
JOIN
( SELECT product_id
, COUNT(*) AS cnt
, SUM(some_other_numeric_value) AS sum_other_values
FROM `product_tags` pt
WHERE some_value = "some comparison value"
GROUP BY product_id
) AS pt
ON p.`product_id` = pt.`product_id`
ORDER BY sum_other_values DESC ;
The COALESCE()
nie są tam ściśle potrzebne - na wypadek gdybyś zmienił wewnętrzne połączenia LEFT
połączenia zewnętrzne.
0 dla odpowiedzi nr 2
nie możesz uporządkować według funkcji sumy
zamiast tego możesz to zrobić w ten sposób
SELECT * ,SUM( pt.`some_other_numeric_value` ) as sumvalues
FROM `product` p
JOIN `product_categories` pc
ON p.`product_id` = pc.`product_id`
JOIN `product_tags` pt
ON p.`product_id` = pt.`product_id`
WHERE pt.`some_value` = "some comparison value"
GROUP BY p.`product_id`
HAVING SUM( pc.`status` ) > 0
ORDER BY sumvalues DESC