两个左连接为MySQL提供了不真实的数据(双数据?)

时间:2013-01-22 23:17:38

标签: mysql sql

这是我的疑问:

SELECT `products`.*, SUM(orders.total_count) AS revenue,
    SUM(orders.quantity) AS qty, ROUND(AVG(product_reviews.stars)) as avg_stars 
FROM `products` 
LEFT JOIN `orders`
    ON (`products`.`id` = `orders`.`product_id`) AND
    (`orders`.`status` = 'delivered' OR `orders`.`status` = 'new') 
LEFT JOIN product_reviews
    ON (products.id = product_reviews.product_id)
GROUP BY `products`.`ID`
ORDER BY products.ID DESC
LIMIT 10
OFFSET 0

当我有第二个左连接时,我的第一个左连接数据,来自订单表的收入和数量给了我根本不正确的值(太高,很多双打?)

来自this question

我得到的方向是我得到半笛卡尔产品,所以对产品的两次评论是数量加倍,我相信这是我的问题。

如何解决这个问题?

3 个答案:

答案 0 :(得分:3)

问题是product_reviews和orders表每个产品ID可以有多一行。解决此问题的一种方法是使用子查询:

SELECT `products`.*, 
  o.revenue,
  o.qty, 
  ROUND(avg_stars) as avg_stars 
FROM `products` 
LEFT JOIN
(
  select `product_id`, 
    sum(total_count) revenue,
    sum(quantity) qty
  from `orders`
  where `status` in ('delivered', 'new')
  group by `product_id`
) o
  ON `products`.`id` = o.`product_id`
LEFT JOIN
(
  select product_id, avg(stars) avg_stars
  from product_reviews
  group by product_id
) pr
    ON (products.id = pr.product_id)
ORDER BY products.ID DESC
LIMIT 10
OFFSET 0

答案 1 :(得分:1)

避免该问题的一种方法是在SELECT列表中使用相关子查询,而不是左连接。

SELECT p.*
     , SUM(o.total_count) AS revenue
     , SUM(o.quantity) AS qty
     , ( SELECT ROUND(AVG(r.stars))
           FROM `product_reviews` r
          WHERE r.product_id = p.id 
       ) AS avg_stars
  FROM `products` p
  LEFT
  JOIN `orders` o
    ON o.product_id = p.id
   AND o.status IN ('delivered','new')
 GROUP BY p.id
 ORDER BY p.id DESC
 LIMIT 10
 OFFSET 0

这不是唯一的方法,并不一定是最好的方法,特别是对于大型集合但是鉴于子查询最多运行10次(给定LIMIT子句),性能应该合理(给定一个合适的方法) product_reviews(product_id,stars)上的索引。

如果要返回所有产品ID或其中很大一部分,那么使用内联视图可能会提供更好的性能(避免在选择列表中执行相关子查询的嵌套循环)

SELECT p.*
     , SUM(o.total_count) AS revenue
     , SUM(o.quantity) AS qty
     , s.avg_stars
  FROM `products` p
  LEFT
  JOIN `orders` o
    ON o.product_id = p.id
   AND o.status IN ('delivered','new')
  LEFT
  JOIN ( SELECT ROUND(AVG(r.stars)) AS avg_stars
              , r.product_id
           FROM `product_reviews` r
          GROUP BY r.product_id 
       ) s
    ON s.product_id = p.id
 GROUP BY p.id
 ORDER BY p.id DESC
 LIMIT 10
 OFFSET 0

请注意:原始查询的问题在于产品的每个订单都与产品的每次审核相匹配。

如果我对“半笛卡尔”一词的使用产生误导或混淆,我道歉。

我想要表达的想法是,您有两个不同的集合(产品的订单集和产品的评论集),并且您的查询生成了“交叉产品”这两个不同的集合,基本上“匹配”每个订单到每个评论(对于特定产品)。

例如,给定product_id 101的reviews中的三行,以及product_id 101的orders中的两行,例如:

REVIEWS
pid  stars text
---  ----- --------------
101  4.5   woo hoo perfect
101  3     ehh
101  1     totally sucked


ORDERS
pid  date   qty 
---  -----  ---
101  1/13   100
101  1/22   7

您的原始查询实际上是在形成一个结果集,其中包含六行,每行从订单匹配到评论中的所有三行:

id   date   qty   stars text
---  ----   ----  ----  ------------
101  1/13   100   4.5   woo hoo perfect
101  1/13   100   3     ehh
101  1/13   100   1     totally sucked
101  1/22   7     4.5   woo hoo perfect
101  1/22   7     3     ehh
101  1/22   7     1     totally sucked

然后,当应用了qty上的SUM聚合时,返回的值比你预期的要大。

答案 2 :(得分:1)

如果没有看到你的表架构,就很难解决这个问题, 我建议你首先查看你的Aggregations和Group By语句,然后查看你的列默认值,你如何处理空值,还要看聚合函数中的DISTINCT。

如果所有其他方法都失败并且“优化”解决方案并不重要且您的数据量很低,那么仅在您需要值的表上进行“子选择”,在“1选择”表中的“选择”行中,您的行数要窄一些范围,它将产生正确的结果。

我建议您在这里提供表格模式。