PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Tabela średniej historii akcji

specjalna trudność tego zadania:nie możesz po prostu wybrać punktów danych w swoim zakresie czasowym, ale musisz wziąć pod uwagę najnowsze punkt danych przed zakres czasu i najwcześniejszy punkt danych za dodatkowo zakres czasu. Różni się to dla każdego wiersza, a każdy punkt danych może istnieć lub nie. Wymaga złożonego zapytania i utrudnia korzystanie z indeksów.

Możesz użyć typów zakresów i operatorzy (Postgres 9.2+ ) aby uprościć obliczenia:

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;

Uwaga, używam nazwy kolumny day zamiast date . Nigdy nie używam podstawowych nazw typów jako nazw kolumn.

W podzapytaniu sub Pobieram dzień z następnego wiersza dla każdego elementu za pomocą funkcji okna lead() , używając wbudowanej opcji, aby domyślnie podać „dzisiaj” tam, gdzie nie ma następnego wiersza.
Dzięki temu tworzę daterange i dopasuj go do danych wejściowych za pomocą operatora nakładania się && , obliczając wynikowy zakres dat za pomocą operatora przecięcia * .

Wszystkie zakresy są tutaj z wyłącznym górna granica. Dlatego dodaję jeden dzień do zakresu wejściowego. W ten sposób możemy po prostu odjąć lower(range) od upper(range) aby uzyskać liczbę dni.

Zakładam, że „wczoraj” to ostatni dzień z wiarygodnymi danymi. „Dzisiaj” wciąż może się zmienić w aplikacji z prawdziwego życia. W związku z tym używam "dzisiaj" (now()::date ) jako wyłączna górna granica dla otwartych zakresów.

Podaję dwa wyniki:

  • your_result zgadza się z wyświetlanymi wynikami.
    Bezwarunkowo dzielisz przez liczbę dni w swoim zakresie dat. Na przykład, jeśli przedmiot jest wystawiony tylko na ostatni dzień, otrzymujesz bardzo niską (mylącą!) „średnią”.

  • my_result oblicza te same lub wyższe liczby.
    Dzielę przez rzeczywistą liczba dni, przez które przedmiot jest wystawiony na aukcję. Na przykład, jeśli przedmiot jest wystawiony tylko na ostatni dzień, zwracam wymienioną wartość jako średnią.

Aby zrozumieć różnicę, dodałem liczbę dni, przez które przedmiot był wystawiany:days_in_range

Skrzypce SQL .

Indeks i wydajność

W przypadku tego rodzaju danych stare wiersze zazwyczaj się nie zmieniają. Byłoby to doskonałym argumentem za zmaterializowanym widokiem :

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;

Następnie możesz dodać indeks GiST, który obsługuje odpowiedni operator && :

CREATE INDEX mv_stock_range_idx ON mv_stock USING gist (day_range);

Duży przypadek testowy

Przeprowadziłem bardziej realistyczny test z 200 tysiącami wierszy. Zapytanie używające MV było około 6 razy szybsze, co z kolei było ~10x szybsze niż zapytanie @Joop. Wydajność w dużym stopniu zależy od dystrybucji danych. MV pomaga najbardziej w przypadku dużych tabel i dużej częstotliwości wpisów. Ponadto, jeśli tabela zawiera kolumny, które nie są istotne dla tego zapytania, MV może być mniejsza. Kwestia kosztów a zysków.

Wszystkie rozwiązania opublikowane do tej pory (i zaadaptowane) umieściłem na wielkich skrzypcach do zabawy:

SQL Fiddle z dużym przypadkiem testowym.
SQL Fiddle z zaledwie 40 tys. wierszy - aby uniknąć przekroczenia limitu czasu na 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. Zamówienie aktualizacji bazy danych

  2. Błąd składni EXECUTE w postgresql

  3. Integracja PostgreSQL z systemami uwierzytelniania

  4. Jak to działa przy użyciu bazy danych PostgreSQL z Dockerem i Flask?

  5. Czy funkcje PostgreSQL są transakcyjne?