具有两个连接的查询中的mysql聚合函数会产生意外结果

Maj*_*jiy 1 mysql join aggregate-functions

给出以下(非常简化的)mysql表结构:

制品

  • ID

产品类别

  • ID
  • PRODUCT_ID
  • 状态(整数)

product_tags

  • ID
  • PRODUCT_ID
  • some_other_numeric_value

我试图找到与某个product_tag有关联的每个产品,并且至少有一个与status-attribute为1的类别的关系.

我尝试了以下查询:

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
Run Code Online (Sandbox Code Playgroud)

现在我的问题是:SUM(pt.some_other_numeric_value)返回意外值.

我意识到,如果有问题的产品有更多然后一个关系到product_categories表,然后每相对于product_tags表计为许多计时因为有关系的product_categories表!

例如:如果id = 1的产品与具有ids = 2,3和4的product_categories的关系,以及与具有ID 5和6的product_tags的关系 - 那么如果我插入a GROUP_CONCAT(pt.id),那么它确实给出了5,6,5 ,6,5,6而不是预期的5,6.

起初我怀疑它是与连接类型(左连接,右连接,内连接,等等)的一个问题,所以我想尽连接类型,我知道的,但无济于事.我还试图在GROUP BY子句中包含更多的id-fields ,但这也没有解决问题.

有人可以向我解释这里究竟出了什么问题吗?

ype*_*eᵀᴹ 5

您通过关系将"main"(product)表连接到两个表(tagscategories)1:n,因此可以预期,您正在创建一个迷你笛卡尔积.对于同时具有多个关联标记和多个关联类别的产品,将在结果集中创建多个行.如果您分组,则在聚合函数中有错误的结果.


避免这种情况的一种方法是删除两个连接中的一个,如果您不需要该表的结果,这是一个有效的startegy.假设您不需要表格SELECT列表中的任何内容product_categories.然后你可以使用半连接(EXISTS subquery)到那个表:

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 ;
Run Code Online (Sandbox Code Playgroud)

避免这个问题的另一种方法是 - 在GROUP BY MainTable.pk- 或在聚合函数DISTINCT内部使用之后.这有效但你无法使用它.因此,它在您的特定查询中没用.COUNT()GROUP_CONCAT()SUM()


第三个选项 - 始终有效 - 首先按两个(或更多)边表分组,然后连接到主表.在你的情况下这样的事情:

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 ;
Run Code Online (Sandbox Code Playgroud)

COALESCE()那里不是严格需要的 - 以防万一你将内部连接与LEFT外部连接组合在一起.