PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Таблица със средна история на акциите

Специална трудност на тази задача:не можете просто да изберете точки от данни във вашия времеви диапазон, но трябва да вземете предвид най-новите точка от данни преди времевия диапазон и най-ранния точка от данни след времевият диапазон допълнително. Това варира за всеки ред и всяка точка от данни може да съществува или да не съществува. Изисква сложна заявка и затруднява използването на индекси.

Можете да използвате типове диапазони и оператори (Postgres 9.2+ ), за да опростите изчисленията:

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;

Имайте предвид, че използвам името на колоната day вместо date . Никога не използвам имена на основни типове като имена на колони.

В подзаявката sub Извличам деня от следващия ред за всеки елемент с прозоречната функция lead() , използвайки вградената опция за предоставяне на „днес“ по подразбиране, където няма следващ ред.
С това формирам daterange и го съпоставете с входа с оператора за припокриване && , изчислявайки получения период от време с оператора за пресичане * .

Всички гами тук са сизключително горна граница. Ето защо добавям един ден към диапазона на въвеждане. По този начин можем просто да извадим lower(range) от upper(range) за да получите броя на дните.

Предполагам, че "вчера" е последният ден с надеждни данни. „Днес“ все още може да се промени в реално приложение. Следователно използвам „днес“ (now()::date ) като изключителна горна граница за отворени диапазони.

Предоставям два резултата:

  • your_result е съгласен с показаните от вас резултати.
    Делите безусловно на броя дни във вашия период от време. Например, ако даден артикул е посочен само за последния ден, получавате много ниска (подвеждаща!) „средна стойност“.

  • my_result изчислява същите или по-големи числа.
    Деля на действителното брой дни в списъка на даден артикул. Например, ако даден артикул е посочен само за последния ден, връщам посочената стойност като средна.

За да осмисля разликата, добавих броя дни, в които артикулът е бил в списъка:days_in_range

SQL Fiddle .

Индекс и ефективност

За този тип данни старите редове обикновено не се променят. Това би било отличен случай за материализиран изглед :

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;

След това можете да добавите GiST индекс, който поддържа съответния оператор && :

CREATE INDEX mv_stock_range_idx ON mv_stock USING gist (day_range);

Голям тестов случай

Проведох по-реалистичен тест с 200k реда. Заявката, използваща MV, беше около 6 пъти по-бърза, което от своя страна беше ~ 10 пъти по-бърза от заявката на @Joop. Производителността силно зависи от разпространението на данни. MV помага най-много при големи маси и висока честота на влизания. Освен това, ако таблицата има колони, които не са подходящи за тази заявка, MV може да бъде по-малък. Въпрос на цена срещу печалба.

Сложих всички решения, публикувани досега (и адаптирани) в голяма цигулка, за да си играя с:

SQL Fiddle с голям тестов случай.
SQL Fiddle само с 40k реда - за избягване на таймаут на 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. Групова актуализация в postgreSQL с помощта на unnest

  2. sql изберете най-ранната дата за множество редове

  3. Regex за PostgreSQL за получаване на домейн с поддомейн от URL/уебсайт

  4. Postgresql генерира_серия от месеци

  5. не може да се свърже с AWS RDS postgres екземпляр от pgadmin4