0

これは私の質問です:

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

この2番目の左結合がある場合、最初の左結合のデータ、収益、および注文テーブルからの数量は、まったく真ではない値を示します(高すぎる、多くの2倍?)

この質問から。

セミデカルト積を取得するという方向性があったので、2回のレビューで数量が2倍になり、これが私の問題だと思います。

これはどのように解決できますか?

4

3 に答える 3

3

問題は、product_reviewsandordersテーブルが製品IDごとに複数の行を持つ可能性があることです。これを修正する1つの方法は、サブクエリを使用することです。

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
于 2013-01-22T23:29:18.343 に答える
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

明確にするために、元のクエリの問題は、製品のすべての注文が製品のすべてのレビューと一致することです。

「半デカルト」という用語の使用が誤解を招いたり混乱させたりした場合は、お詫び申し上げます。

それによって私が伝えようとしたのは、2つの異なるセット(製品の注文のセットと製品のレビューのセット)があり、クエリがこれら2つの異なる「クロス積」を生成しているということでした。セット、基本的にすべての注文をすべてのレビューに「一致」させます(特定の製品について)。

たとえば、product_id 101に3行、reviewsproduct_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集計が適用されると、返される値は予想よりもはるかに大きくなります。

于 2013-01-22T23:34:00.277 に答える
1

テーブルスキーマを確認せずにこれを解決するのは簡単ではありません。最初に集計とGroupByステートメントを確認してから、列のデフォルト値、空の値をどのように処理しているか、集計関数のDISTINCTも確認することをお勧めします。

他のすべてが失敗し、「最適化された」ソリューションが不可欠ではなく、データ量が少ない場合は、値が必要なテーブルでのみサブ選択を実行します。1つのテーブルのサブ選択では、行スコープがはるかに狭くなります。正しい結果が得られます。

ここにテーブルスキーマを指定することをお勧めします。

于 2013-01-22T23:51:25.063 に答える