/ / mysql funkcje agregujące w zapytaniu z dwoma połączeniami dają nieoczekiwane wyniki - mysql, join, funkcje agregujące

Funkcje agregujące mysql w zapytaniu z dwoma złączeniami dają nieoczekiwane wyniki - mysql, join, aggregate-functions

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 № 1

Dołą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