PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Tabel riwayat stok rata-rata

Kesulitan khusus tugas ini:Anda tidak bisa hanya memilih titik data dalam rentang waktu Anda, tetapi harus mempertimbangkan terbaru titik data sebelum rentang waktu dan paling awal titik data setelah rentang waktu tambahan. Ini bervariasi untuk setiap baris dan setiap titik data mungkin ada atau tidak ada. Memerlukan kueri yang canggih dan mempersulit penggunaan indeks.

Anda dapat menggunakan jenis rentang dan operator (Postgres 9.2+ ) untuk menyederhanakan perhitungan:

WITH input(a,b) AS (SELECT '2013-01-01'::date  -- your time frame here
                         , '2013-01-15'::date) -- inclusive borders
SELECT store_id, product_id
     , sum(upper(days) - lower(days))                    AS days_in_range
     , round(sum(value * (upper(days) - lower(days)))::numeric
                    / (SELECT b-a+1 FROM input), 2)      AS your_result
     , round(sum(value * (upper(days) - lower(days)))::numeric
                    / sum(upper(days) - lower(days)), 2) AS my_result
FROM (
   SELECT store_id, product_id, value, s.day_range * x.day_range AS days
   FROM  (
      SELECT store_id, product_id, value
           , daterange (day, lead(day, 1, now()::date)
             OVER (PARTITION BY store_id, product_id ORDER BY day)) AS day_range 
      FROM   stock
      ) s
   JOIN  (
      SELECT daterange(a, b+1) AS day_range
      FROM   input
      ) x ON s.day_range && x.day_range
   ) sub
GROUP  BY 1,2
ORDER  BY 1,2;

Catatan, saya menggunakan nama kolom day bukannya date . Saya tidak pernah menggunakan nama tipe dasar sebagai nama kolom.

Di subkueri sub Saya mengambil hari dari baris berikutnya untuk setiap item dengan fungsi jendela lead() , menggunakan opsi bawaan untuk memberikan "hari ini" sebagai default di mana tidak ada baris berikutnya.
Dengan ini saya membentuk daterange dan cocokkan dengan input dengan operator tumpang tindih && , menghitung rentang tanggal yang dihasilkan dengan operator persimpangan * .

Semua rentang di sini dengan eksklusif batas atas. Itu sebabnya saya menambahkan satu hari ke rentang input. Dengan cara ini kita cukup mengurangi lower(range) dari upper(range) untuk mendapatkan jumlah hari.

Saya berasumsi bahwa "kemarin" adalah hari terakhir dengan data yang dapat diandalkan. "Hari ini" masih bisa berubah dalam aplikasi kehidupan nyata. Akibatnya, saya menggunakan "hari ini" (now()::date ) sebagai batas atas eksklusif untuk rentang terbuka.

Saya memberikan dua hasil:

  • your_result setuju dengan hasil yang ditampilkan.
    Anda membagi dengan jumlah hari dalam rentang tanggal tanpa syarat. Misalnya, jika item hanya terdaftar untuk hari terakhir, Anda mendapatkan "rata-rata" yang sangat rendah (menyesatkan!).

  • my_result menghitung angka yang sama atau lebih tinggi.
    Saya bagi dengan sebenarnya jumlah hari item terdaftar. Misalnya, jika item hanya terdaftar untuk hari terakhir, saya mengembalikan nilai yang terdaftar sebagai rata-rata.

Untuk memahami perbedaannya, saya menambahkan jumlah hari item tersebut terdaftar:days_in_range

SQL Fiddle .

Indeks dan kinerja

Untuk jenis data ini, baris lama biasanya tidak berubah. Ini akan menjadi kasus yang sangat baik untuk tampilan yang terwujud :

CREATE MATERIALIZED VIEW mv_stock AS
SELECT store_id, product_id, value
     , daterange (day, lead(day, 1, now()::date) OVER (PARTITION BY store_id, product_id
                                                       ORDER BY day)) AS day_range
FROM   stock;

Kemudian Anda dapat menambahkan Indeks Intisari yang mendukung operator terkait && :

CREATE INDEX mv_stock_range_idx ON mv_stock USING gist (day_range);

Kasus uji besar

Saya menjalankan tes yang lebih realistis dengan 200 ribu baris. Kueri yang menggunakan MV sekitar 6 kali lebih cepat, yang pada gilirannya ~ 10x lebih cepat dari kueri @Joop. Kinerja sangat tergantung pada distribusi data. MV paling membantu dengan tabel besar dan frekuensi entri yang tinggi. Juga, jika tabel memiliki kolom yang tidak relevan dengan kueri ini, MV bisa lebih kecil. Pertanyaan tentang biaya vs. keuntungan.

Saya telah menempatkan semua solusi yang diposting sejauh ini (dan diadaptasi) ke dalam biola besar untuk dimainkan:

SQL Fiddle dengan kasus uji besar.
SQL Fiddle hanya dengan 40k baris - untuk menghindari batas waktu di sqlfiddle.com



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Inner join versus melakukan a where dalam klausa

  2. Apakah ada opsi untuk bergabung ke tabel untuk asosiasi banyak-ke-banyak?

  3. GABUNG Beberapa Tabel berdasarkan stempel waktu dan kondisi lain

  4. Fungsi agregat nilai pertama dan terakhir di postgresql yang berfungsi dengan benar dengan nilai NULL

  5. Mengonversi hubungan banyak-ke-banyak menjadi satu-ke-banyak di PostgreSQL