Zvláštní obtížnost tohoto úkolu:nemůžete jen vybrat datové body ve svém časovém rozsahu, ale musíte vzít v úvahu nejnovější datový bod před časový rozsah a nejstarší datový bod po časový rozsah dodatečně. To se liší pro každý řádek a každý datový bod může nebo nemusí existovat. Vyžaduje sofistikovaný dotaz a ztěžuje použití indexů.
Můžete použít typy rozsahů a operátoři (Postgres 9.2+ ) pro zjednodušení výpočtů:
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;
Všimněte si, že používám název sloupce day
místo date
. Nikdy nepoužívám základní názvy typů jako názvy sloupců.
V poddotazu sub
Pro každou položku načtu den z dalšího řádku pomocí funkce okna lead()
, pomocí vestavěné možnosti poskytnout „dnes“ jako výchozí tam, kde není žádný další řádek.
Tím vytvořím daterange
a porovnejte jej se vstupem s operátorem překrytí &&
, přičemž výsledné časové období vypočítáte pomocí operátora křižovatky *
.
Všechny zde uvedené rozsahy jsou s exkluzivním horní okraj. Proto přidávám jeden den do vstupního rozsahu. Tímto způsobem můžeme jednoduše odečíst lower(range)
z upper(range)
získat počet dní.
Předpokládám, že „včera“ je poslední den se spolehlivými údaji. "Dnešek" se stále může změnit v reálném životě. V důsledku toho používám „dnes“ (now()::date
) jako exkluzivní horní hranice pro otevřené rozsahy.
Poskytuji dva výsledky:
-
your_result
souhlasí s vašimi zobrazenými výsledky.
Vydělíte bezpodmínečně počtem dní ve vašem časovém období. Pokud je například položka uvedena pouze za poslední den, dostanete velmi nízký (zavádějící!) „průměr“. -
my_result
počítá stejná nebo vyšší čísla.
Dělím skutečným počet dní, kdy je položka uvedena. Pokud je například položka uvedena pouze za poslední den, vrátím uvedenou hodnotu jako průměr.
Abych pochopil rozdíl, přidal jsem počet dní, po které byla položka uvedena:days_in_range
Index a výkon
U tohoto druhu dat se staré řádky obvykle nemění. To by byl skvělý případ pro materializovaný pohled :
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;
Poté můžete přidat index GiST, který podporuje příslušný operátor &&
:
CREATE INDEX mv_stock_range_idx ON mv_stock USING gist (day_range);
Velký testovací případ
Provedl jsem realističtější test s 200 000 řádky. Dotaz pomocí MV byl asi 6krát rychlejší, což zase bylo ~ 10krát rychlejší než dotaz @Joop. Výkon silně závisí na distribuci dat. MV nejvíce pomáhá s velkými stoly a vysokou frekvencí vstupů. Pokud tabulka obsahuje sloupce, které nejsou pro tento dotaz relevantní, může být MV menší. Otázka ceny vs. zisku.
Všechna dosud zveřejněná řešení (a upravená) jsem vložil do velkých houslí, abych si s nimi mohl hrát:
SQL Fiddle s velkým testovacím případem.
SQL Fiddle s pouze 40 000 řádky
- abyste se vyhnuli vypršení časového limitu na sqlfiddle.com