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

时间:2013-01-03 13:56:03

标签: mysql join aggregate-functions

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

产品

  • ID

product_categories

  • ID
  • PRODUCT_ID
  • status(整数)

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

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

我意识到如果有问题的产品与 product_categories 表格有多个关系,那么与 product_tags 表格的每个关系都会被计算为与与 product_categories 表的关系!

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

起初我怀疑这是连接类型(左连接,右连接,内连接等)的问题,所以我尝试了我所知道的每种连接类型,但无济于事。我还尝试在GROUP BY子句中包含更多id-fields,但这也没有解决问题。

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

2 个答案:

答案 0 :(得分:5)

您通过product关系将“主”(tags)表加入两个表(categories1: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 ;

解决此问题的另一种方法是 - 在GROUP BY MainTable.pk之后 - 在DISTINCTCOUNT()聚合函数中使用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 ;

在那里并不严格需要COALESCE() - 以防你将内部联接设置为LEFT外连接。

答案 1 :(得分:0)

你不能用和函数来命令

相反,你可以这样做

 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