避免该问题的一种方法是在 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 聚合时,返回的值比您预期的要大得多。