スキーマ:
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)
;
だから、私たちは持っています:
今、私は以下の質問に対する答えを得たいです:
次のクエリでは( 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行あることを理解しています(他の一致の場合も同様)。しかし、適切なクエリを作成する方法がわかりません。
質問:
PostgreSQLバージョン:9.6.1
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
_です。)
items.created
_でフィルタリングしたい場合はどうすればよいですか?場合によります。同じフィルターを_payments.created
_と_extras.created
_に適用できますか?
はいの場合は、サブクエリにもフィルタを追加します。 (この場合はありそうにありません。)
いいえの場合でも、ほとんどのアイテムを選択しているため、上記のクエリが最も効率的です。サブクエリの一部の集計は結合で削除されますが、それでも、より複雑なクエリよりも安価です。
いいえの場合、アイテムの小さい部分を選択しているので、相関サブクエリまたはLATERAL
結合をお勧めします。例: