web-dev-qa-db-ja.com

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

スキーマ

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 JOINの場合、items.id = 1のアイテムには2行あることを理解しています(他の一致の場合も同様)。しかし、適切なクエリを作成する方法がわかりません。

質問

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

PostgreSQLバージョン:9.6.1

10
Stranger6667

paymentsごとに複数のextrasおよび複数のitemが存在する可能性があるため、「プロキシクロス結合」これら2つのテーブルの間。 itemに結合する前に_item_id_beforeごとに行を集計すると、すべて正しいはずです。

_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フィドル。

しかし、_items.created_でフィルタリングしたい場合はどうすればよいですか?

コメントへの対応

場合によります。同じフィルターを_payments.created_と_extras.created_に適用できますか?

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

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

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

9