Skema :
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
);
Data :
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)
;
Jadi kita punya:
- 3 item dalam CZ dalam 1 dalam PL
- 370 diperoleh di CZ dan 25 di PL
- 350 biaya dalam CZ dan 20 dalam PL
- 11 ekstra diperoleh di CZ dan 5 ekstra di PL
Sekarang saya ingin mendapatkan jawaban untuk pertanyaan-pertanyaan berikut:
- Berapa banyak barang yang kami miliki bulan lalu di setiap negara?
- Berapa jumlah total yang diterima (jumlah pembayaran. Jumlah) di setiap negara?
- Berapa total biaya (jumlah barang.harga) di setiap negara?
- Berapa total penghasilan tambahan (jumlah extras.amount) di setiap negara?
Dengan kueri berikut ( 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;
Hasilnya salah:
group_by | item_count | cost | earned | extra_earned
----------+------------+--------+--------+--------------
CZ | 3 | 450.00 | 370.00 | 16.00
PL | 1 | 40.00 | 50.00 | 5.00
Biaya dan extra_earned untuk CZ tidak valid - 450 bukannya 350 dan 16 bukannya 11. Biaya dan diperoleh untuk PL juga tidak valid - mereka digandakan.
Saya mengerti, bahwa dalam kasus LEFT OUTER JOIN
akan ada 2 baris untuk item dengan items.id = 1 (dan seterusnya untuk pertandingan lainnya), tetapi saya tidak tahu bagaimana membangun kueri yang tepat.
Pertanyaan :
- Bagaimana menghindari hasil yang salah dalam agregasi dalam kueri di beberapa tabel?
- Apa cara terbaik untuk menghitung penjumlahan dari nilai yang berbeda (items.id dalam kasus itu)?
Versi PostgreSQL : 9.6.1
postgresql
join
aggregate
Stranger6667
sumber
sumber
OUTER APPLY
dan menggunakanLATERAL
gabungan.Seq Scan
pembayaran, yang berarti bahwa statistik akan dihitung ulang pada semua item. Saya tidak menyebutkan ini dalam pertanyaan, tetapi saya juga ingin memfilter item berdasarkan waktu pembuatan, jadi saya hanya perlu subkumpulan data agregat tertentu. Saya akan memperbarui pertanyaanWHERE
klausa atau bergabung di subqueries. Tetapi periksa opsi 4 juga, menggunakanLATERAL
.payments
danitems
dalam subquery dan menambahkannyaWHERE
? Saya harus membandingkan semua opsi :)items.created_at
, ya.Jawaban:
Karena bisa ada beberapa
payments
dan beberapaextras
peritem
, Anda mengalami "gabungan lintas proxy" antara dua tabel tersebut. Baris agregat peritem_id
sebelum bergabung keitem
dan semuanya harus benar:Perhatikan contoh "pasar ikan":
Tepatnya,
SUM(i.price)
akan salah setelah bergabung ke n-tabel tunggal, yang mengalikan setiap harga dengan jumlah baris terkait. Melakukannya dua kali hanya memperburuknya - dan juga berpotensi mahal secara komputasi.Oh, dan karena kita tidak menggandakan baris
items
sekarang, kita bisa menggunakan yang lebih murahcount(*)
daripadacount(DISTINCT i.id)
. (id
sedangNOT NULL PRIMARY KEY
.)SQL Fiddle.
Tetapi jika saya ingin memfilter
items.created
?Mengatasi komentar Anda.
Tergantung. Bisakah kita menerapkan filter yang sama ke
payments.created
danextras.created
?Jika ya, tambahkan filter di subqueries juga. (Sepertinya tidak mungkin dalam kasus ini.)
Jika tidak, tetapi kami masih memilih sebagian besar item , kueri di atas masih tetap paling efisien. Beberapa agregasi dalam subkueri dihilangkan dalam gabungan, tapi itu masih lebih murah daripada kueri yang lebih kompleks.
Jika tidak, dan kami memilih sebagian kecil item, saya sarankan subqueries yang berkorelasi atau
LATERAL
bergabung. Contoh:sumber
items.created
apa yang paling efisien untuk melakukan ini? Apakah saya harus menambahkan ekstraJOIN
padaitems
ke subqueries (p
dane
dalam contoh Anda) untuk melakukan filtrasi seperti @ ypercubeᵀᴹ disebutkan?LATERAL JOIN
bekerja untukku! Terima kasih atas penjelasan bersihnya :)