複数の結合を持つ個別の行の合計


10

スキーマ

CREATE TABLE "items" (
  "id"            SERIAL                   NOT NULL PRIMARY KEY,
  "country"       VARCHAR(2)               NOT NULL,
  "created"       TIMESTAMP WITH TIME ZONE NOT NULL,
  "price"         NUMERIC(11, 2)           NOT NULL
);
CREATE TABLE "payments" (
  "id"      SERIAL                   NOT NULL PRIMARY KEY,
  "created" TIMESTAMP WITH TIME ZONE NOT NULL,
  "amount"  NUMERIC(11, 2)           NOT NULL,
  "item_id" INTEGER                  NULL
);
CREATE TABLE "extras" (
  "id"      SERIAL                   NOT NULL PRIMARY KEY,
  "created" TIMESTAMP WITH TIME ZONE NOT NULL,
  "amount"  NUMERIC(11, 2)           NOT NULL,
  "item_id" INTEGER                  NULL
);

データ

INSERT INTO items VALUES
  (1, 'CZ', '2016-11-01', 100),
  (2, 'CZ', '2016-11-02', 100),
  (3, 'PL', '2016-11-03', 20),
  (4, 'CZ', '2016-11-04', 150)
;
INSERT INTO payments VALUES
  (1, '2016-11-01', 60, 1),
  (2, '2016-11-01', 60, 1),
  (3, '2016-11-02', 100, 2),
  (4, '2016-11-03', 25, 3),
  (5, '2016-11-04', 150, 4)
;
INSERT INTO extras VALUES
  (1, '2016-11-01', 5, 1),
  (2, '2016-11-02', 1, 2),
  (3, '2016-11-03', 2, 3),
  (4, '2016-11-03', 3, 3),
  (5, '2016-11-04', 5, 4)
;

だから、私たちは持っています:

  • PLの1のCZの3アイテム
  • CZで370、PLで25
  • CZで350、PLで20
  • CZで11の追加獲得、PLで5の追加獲得

今、私は以下の質問に対する答えを得たいです:

  1. 先月、どの国にいくつアイテムがありましたか?
  2. 各国で獲得した合計金額(payments.amountsの合計)は?
  3. 各国の合計費用(items.priceの合計)はどれくらいですか?
  4. 各国の追加の総収入(extras.amountの合計)はどれくらいでしたか?

次のクエリ(SQLFiddle)の場合:

SELECT
  country                  AS "group_by",
  COUNT(DISTINCT items.id) AS "item_count",
  SUM(items.price)         AS "cost",
  SUM(payments.amount)     AS "earned",
  SUM(extras.amount)       AS "extra_earned"
FROM items
  LEFT OUTER JOIN payments ON (items.id = payments.item_id)
  LEFT OUTER JOIN extras ON (items.id = extras.item_id)
GROUP BY 1;

結果は間違っています:

 group_by | item_count |  cost  | earned | extra_earned
----------+------------+--------+--------+--------------
 CZ       |          3 | 450.00 | 370.00 |        16.00
 PL       |          1 |  40.00 |  50.00 |         5.00

CZのコストとextra_earnedは無効です-350ではなく450と11ではなく16です。PLのコストと獲得額も無効です-それらは2倍になります。

私は、LEFT OUTER JOINitems.id = 1(他の一致の場合も同様)のアイテムの行が2行ある場合は理解しますが、適切なクエリを作成する方法がわかりません。

質問

  1. 複数のテーブルでのクエリの集計で誤った結果を回避するにはどうすればよいですか?
  2. 個別の値(その場合はitem.id)の合計を計算する最良の方法は何ですか?

PostgreSQLバージョン:9.6.1


ここに私の答えでオプション3を参照してください:dba.stackexchange.com/questions/17012/help-with-this-query/...は、あなたはまた、書き換えることでオプション4を行うことができますOUTER APPLYし、使用してLATERAL代わりに参加します。
ypercubeᵀᴹ

オプション3は機能しますが、その場合Seq Scanは支払い時に必要になります。つまり、統計はすべてのアイテムで再計算されます。質問ではこれについて言及しませんでしたが、作成時間でアイテムをフィルター処理したいので、集計データの特定のサブセットのみが必要になります。質問を更新します
Stranger6667

WHEREサブクエリに句または結合を追加できます。ただし、オプション4もチェックしてくださいLATERAL
ypercubeᵀᴹ

あなたはJOIN paymentsitemsサブクエリでそれを追加WHERE することを意味しますか?すべてのオプションをベンチマークする必要があります:)
Stranger6667

に基づいてサブセットを制限する場合はitems.created_at、はい。
ypercubeᵀᴹ

回答:


9

複数存在することができますのでpayments、複数のextrasあたりitem、あなたがに実行し、「プロキシクロス参加する」これら2つのテーブル間。結合するitem_id に行ごと行を集計するitemと、すべて正しいはずです。

SELECT i.country         AS group_by
     , COUNT(*)          AS item_count
     , SUM(i.price)      AS cost
     , SUM(p.sum_amount) AS earned
     , SUM(e.sum_amount) AS extra_earned
FROM  items i
LEFT  JOIN (
   SELECT item_id, SUM(amount) AS sum_amount
   FROM   payments
   GROUP  BY 1
   ) p ON p.item_id = i.id
LEFT  JOIN (
   SELECT item_id, SUM(amount) AS sum_amount
   FROM   extras
   GROUP  BY 1
   ) e ON e.item_id = i.id
GROUP BY 1;

「fishmarket」の例を考えてみましょう:

正確にSUM(i.price)は、各価格に関連する行の数を乗算する単一のnテーブルに結合した後は正しくありません。2度実行すると、状況が悪化し、計算コストが高くなる可能性もあります。

ああ、items今は行を乗算しないので、のcount(*)代わりに安価なものを使用できcount(DISTINCT i.id)ます。(であるidことNOT NULL PRIMARY KEY

SQL Fiddle。

しかし、私がフィルタリングしたい場合はitems.created

コメントへの対応。

場合によります。同じフィルターをpayments.createdandに適用できますextras.createdか?

はいの場合、サブクエリにもフィルタを追加します。(この場合はありそうにありません。)

「いいえ」の場合でも、ほとんどのアイテムを選択していますが、上記のクエリが最も効率的です。サブクエリの一部の集計は結合で削除されますが、それでも複雑なクエリよりも安価です。

いいえの場合、アイテムのごく一部を選択しているので、相関サブクエリまたはLATERAL結合をお勧めします。例:


答えてくれてありがとう!しかし、items.createdこれを行う最も効率的な方法は何かでフィルタリングしたい場合はどうすればよいですか?私は、余分な追加する必要がありますJOIN上のitemsサブクエリに(pそしてeあなたの例では)ypercubeᵀᴹが言及@のようなろ過を行うには?
Stranger6667

@ Stranger6667:状況によります。そして、それは実際には別の質問です。上記に回答を追加しました。
Erwin Brandstetter 2016年

LATERAL JOIN私のために働く!きれいな説明ありがとうございます:)
Stranger6667
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.