この問題を回避するための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
明確にするために、元のクエリの問題は、製品のすべての注文が製品のすべてのレビューと一致することです。
「半デカルト」という用語の使用が誤解を招いたり混乱させたりした場合は、お詫び申し上げます。
それによって私が伝えようとしたのは、2つの異なるセット(製品の注文のセットと製品のレビューのセット)があり、クエリがこれら2つの異なる「クロス積」を生成しているということでした。セット、基本的にすべての注文をすべてのレビューに「一致」させます(特定の製品について)。
たとえば、product_id 101に3行、reviews
product_id 101に2行を指定すると、次のようになり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
元のクエリは基本的に、6行の結果セットを形成しており、順序の各行はレビューの3行すべてに一致しています。
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
次に、数量のSUM集計が適用されると、返される値は予想よりもはるかに大きくなります。