W bazie danych transakcji obejmującej 1000 podmiotów w ciągu 18 miesięcy chciałbym uruchomić zapytanie w celu grupowania każdego możliwego 30-dniowego okresu entity_id
z sumą ich kwot transakcji i COUNT ich transakcji w tym 30-dniowym okresie, oraz zwrócić dane w sposób, który mogę następnie wykonać zapytanie. Po wielu testach ten kod osiąga wiele z tego, czego chcę:
SELECT id, trans_ref_no, amount, trans_date, entity_id,
SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
COUNT(id) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
FROM transactiondb;
I użyję w większym zapytaniu o strukturze podobnej do:
SELECT * FROM (
SELECT id, trans_ref_no, amount, trans_date, entity_id,
SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
COUNT(id) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
FROM transactiondb ) q
WHERE trans_count >= 4
AND trans_total >= 50000;
Przypadek, którego to zapytanie nie obejmuje, ma miejsce, gdy transakcje liczą się przez wiele miesięcy, ale nadal występują w odstępie 30 dni od siebie. Czy tego typu zapytania są możliwe w Postgres? Jeśli tak, z zadowoleniem przyjmuję wszelkie uwagi. Wiele innych tematów omawia „ uruchomione ” agregaty, a nie ciągłe .
Aktualizacja
CREATE TABLE
Skrypt:
CREATE TABLE transactiondb (
id integer NOT NULL,
trans_ref_no character varying(255),
amount numeric(18,2),
trans_date date,
entity_id integer
);
Przykładowe dane można znaleźć tutaj . Korzystam z PostgreSQL 9.1.16.
Wyjście idealny obejmowałyby SUM(amount)
i COUNT()
wszystkich transakcji ponad toczenia 30-dniowego okresu. Zobacz ten obraz, na przykład:
Podświetlenie zielonej daty wskazuje, co obejmuje moje zapytanie. Żółte podświetlanie wierszy wskazuje rekordy, które chciałbym stać się częścią zestawu.
Poprzednie czytanie:
źródło
every possible 30-day period by entity_id
ciebie znaczy termin może rozpocząć każdy dzień, więc 365 możliwych okresów w A (non-przestępnym) roku? A może chcesz traktować dni z faktyczną transakcją jako początek każdego okresu indywidualnieentity_id
? Tak czy inaczej, proszę podać definicję tabeli, wersję Postgres, niektóre przykładowe dane i oczekiwany wynik dla próbki.entity_id
w 30-dniowym oknie, zaczynając od każdej faktycznej transakcji. Czy może istnieć wiele transakcji dla tego samego(trans_date, entity_id)
lub czy ta kombinacja jest zdefiniowana jako unikalna? Twoja definicja tabeli nie maUNIQUE
ograniczenia lub PK, ale wydaje się, że brakuje ograniczeń ...id
klucza podstawowego. Może istnieć wiele transakcji na jednostkę na dzień.Odpowiedzi:
Zapytanie, które masz
Możesz uprościć zapytanie, używając
WINDOW
klauzuli, ale to tylko skraca składnię, a nie zmienia planu zapytań.count(*)
, skoro naid
pewno jest zdefiniowaneNOT NULL
?ORDER BY entity_id
już tego robićPARTITION BY entity_id
Możesz jednak uprościć: w
ogóle nie dodawaj
ORDER BY
do definicji okna, nie dotyczy to twojego zapytania. Wówczas nie musisz definiować niestandardowej ramy okna:Prostsze, szybsze, ale nadal po prostu lepsza wersja tego, co mają , ze statycznymi miesięcy.
Zapytanie, które możesz chcieć
... nie jest jasno zdefiniowane, więc oprę się na następujących założeniach:
Policz transakcje i kwoty za każdy 30-dniowy okres w ramach pierwszej i ostatniej transakcji
entity_id
. Wyklucz początkowe i końcowe okresy bez aktywności, ale uwzględnij wszystkie możliwe 30-dniowe okresy w tych zewnętrznych granicach.Zawiera listę wszystkich 30-dniowych okresów dla każdego
entity_id
z twoimi agregatami itrans_date
jako pierwszy dzień (włącznie) okresu. Aby uzyskać wartości dla każdego wiersza, ponownie połącz się z tabelą bazową ...Podstawowa trudność jest taka sama, jak omówiono tutaj:
Definicja ramki okna nie może zależeć od wartości bieżącego wiersza.
A raczej zadzwoń
generate_series()
ztimestamp
wejściem:Zapytanie, którego naprawdę chcesz
Po aktualizacji pytania i dyskusji:
kumuluj te same wiersze
entity_id
w 30-dniowym oknie, zaczynając od każdej faktycznej transakcji.Ponieważ Twoje dane są rozproszone rzadko, powinno być bardziej wydajne przeprowadzenie samozłączenia z warunkiem zasięgu , tym bardziej, że Postgres 9.1 nie ma jeszcze
LATERAL
sprzężeń:SQL Fiddle.
Kroczące okno może mieć sens (w odniesieniu do wydajności) tylko w przypadku danych przez większość dni.
To robi a nie zbiorcze duplikaty w
(trans_date, entity_id)
ciągu dnia, ale wszystkie wiersze tego samego dnia są zawsze włączone w oknie 30-dniowego.W przypadku dużego stołu taki indeks pokrycia może nieco pomóc:
Ostatnia kolumna
amount
jest przydatna tylko wtedy, gdy wykonasz z niej tylko indeksy. W przeciwnym razie upuść.Ale i tak nie będzie używany, gdy wybierzesz cały stół. Obsługuje zapytania dotyczące małego podzbioru.
źródło
column "t0.amount" must appear in the GROUP BY clause...