Itu sangat tergantung pada keadaan dan persyaratan yang tepat. Pertimbangkan komentar saya untuk pertanyaan itu .
Solusi sederhana
Dengan DISTINCT ON
di Postgres:
SELECT DISTINCT ON (i.good, i.the_date)
i.the_date, p.the_date AS pricing_date, i.good, p.price
FROM inventory i
LEFT JOIN price p ON i.good = p.good AND i.the_date >= p.the_date
ORDER BY i.good, i.the_date, p.the_date DESC;
Memesan hasil.
Atau dengan NOT EXISTS
dalam SQL standar (berfungsi dengan setiap RDBMS yang saya tahu):
SELECT i.the_date, p.the_date AS pricing_date, i.good, i.quantity, p.price
FROM inventory i
LEFT JOIN price p ON p.good = i.good AND p.the_date <= i.the_date
WHERE NOT EXISTS (
SELECT 1 FROM price p1
WHERE p1.good = p.good
AND p1.the_date <= i.the_date
AND p1.the_date > p.the_date
);
Hasil yang sama, tetapi dengan urutan sortir yang sewenang-wenang - kecuali jika Anda menambahkan ORDER BY
.
Bergantung pada distribusi data, persyaratan dan indeks yang tepat, salah satu dari ini mungkin lebih cepat.
Secara umum, DISTINCT ON
adalah pemenang dan Anda mendapatkan hasil yang diurutkan di atasnya. Tetapi untuk kasus-kasus tertentu teknik kueri lainnya (jauh) lebih cepat. Lihat di bawah.
Solusi dengan subqueries untuk menghitung nilai max / min umumnya lebih lambat. Varian dengan CTE pada umumnya lebih lambat.
Pandangan polos (seperti yang diajukan oleh jawaban lain) tidak membantu kinerja sama sekali di Postgres.
SQL Fiddle.
Solusi yang tepat
String dan collation
Pertama-tama, Anda menderita tata letak tabel yang kurang optimal. Ini mungkin tampak sepele, tetapi menormalkan skema Anda bisa sangat membantu.
Penyortiran berdasarkan tipe karakter ( text
,, varchar
...) harus dilakukan sesuai dengan lokal - KOLASI khususnya. Kemungkinan besar DB Anda menggunakan beberapa aturan lokal (seperti, dalam kasus saya:) de_AT.UTF-8
. Cari tahu dengan:
SHOW lc_collate;
Ini membuat pengurutan dan pencarian indeks lebih lambat . Semakin lama string Anda (nama barang) semakin buruk. Jika Anda sebenarnya tidak peduli dengan aturan pengumpulan di output Anda (atau urutan pengurutan sama sekali), ini bisa lebih cepat jika Anda menambahkan COLLATE "C"
:
SELECT DISTINCT ON (i.good COLLATE "C", i.the_date)
i.the_date, p.the_date AS pricing_date, i.good, p.price
FROM inventory i
LEFT JOIN price p ON i.good = p.good AND i.the_date >= p.the_date
ORDER BY i.good COLLATE "C", i.the_date, p.the_date DESC;
Perhatikan bagaimana saya menambahkan collation di dua tempat.
Dua kali lebih cepat dalam pengujian saya dengan masing-masing 20k baris dan nama yang sangat mendasar ('good123').
Indeks
Jika kueri Anda seharusnya menggunakan indeks, kolom dengan data karakter harus menggunakan good
susunan yang cocok ( dalam contoh):
CREATE INDEX inventory_good_date_desc_collate_c_idx
ON price(good COLLATE "C", the_date DESC);
Pastikan untuk membaca dua bab terakhir dari jawaban terkait ini di SO:
Anda bahkan dapat memiliki beberapa indeks dengan susunan berbeda pada kolom yang sama - jika Anda juga membutuhkan barang yang diurutkan menurut susunan lain (atau bawaan) dalam kueri lain.
Normalisasi
String berlebihan (nama yang baik) juga mengasapi tabel dan indeks Anda, yang membuat semuanya lebih lambat. Dengan tata letak tabel yang tepat Anda bisa menghindari sebagian besar masalah untuk memulai. Bisa terlihat seperti ini:
CREATE TABLE good (
good_id serial PRIMARY KEY
, good text NOT NULL
);
CREATE TABLE inventory (
good_id int REFERENCES good (good_id)
, the_date date NOT NULL
, quantity int NOT NULL
, PRIMARY KEY(good_id, the_date)
);
CREATE TABLE price (
good_id int REFERENCES good (good_id)
, the_date date NOT NULL
, price numeric NOT NULL
, PRIMARY KEY(good_id, the_date));
Kunci utama secara otomatis menyediakan (hampir) semua indeks yang kita butuhkan.
Bergantung pada detail yang hilang, indeks multikolom aktif price
dengan urutan menurun pada kolom kedua dapat meningkatkan kinerja:
CREATE INDEX price_good_date_desc_idx ON price(good, the_date DESC);
Sekali lagi, pemeriksaan harus sesuai dengan query Anda (lihat di atas).
Dalam Postgres 9.2 atau lebih baru "indeks penutup" untuk pindaian indeks saja bisa membantu lebih - terutama jika tabel Anda memiliki kolom tambahan, membuat tabel secara substansial lebih besar dari indeks penutup.
Kueri yang dihasilkan ini jauh lebih cepat:
TIDAK ADA
SELECT i.the_date, p.the_date AS pricing_date, g.good, i.quantity, p.price
FROM inventory i
JOIN good g USING (good_id)
LEFT JOIN price p ON p.good_id = i.good_id AND p.the_date <= i.the_date
AND NOT EXISTS (
SELECT 1 FROM price p1
WHERE p1.good_id = p.good_id
AND p1.the_date <= i.the_date
AND p1.the_date > p.the_date
);
HUBUNGI ON
SELECT DISTINCT ON (i.the_date)
i.the_date, p.the_date AS pricing_date, g.good, i.quantity, p.price
FROM inventory i
JOIN good g USING (good_id)
LEFT JOIN price p ON p.good_id = i.good_id AND p.the_date <= i.the_date
ORDER BY i.the_date, p.the_date DESC;
SQL Fiddle.
Solusi lebih cepat
Jika itu masih belum cukup cepat, mungkin ada solusi yang lebih cepat.
CTE rekursif / JOIN LATERAL
/ subquery berkorelasi
Khusus untuk distribusi data dengan banyak harga per barang :
Tampilan terwujud
Jika Anda perlu menjalankan ini sering dan cepat, saya sarankan Anda membuat tampilan terwujud. Saya pikir aman untuk mengasumsikan, bahwa harga dan inventaris untuk tanggal yang lalu jarang berubah. Hitung hasilnya sekali dan simpan snapshot sebagai tampilan terwujud.
Postgres 9.3+ memiliki dukungan otomatis untuk pandangan terwujud. Anda dapat dengan mudah mengimplementasikan versi dasar di versi yang lebih lama.