In einer Datenbank mit Transaktionen, die über einen Zeitraum von 18 Monaten Tausende von Entitäten umfasst, möchte ich eine Abfrage ausführen, um jeden möglichen 30-Tage-Zeitraum entity_id
mit einer Summe ihrer Transaktionsbeträge und COUNT ihrer Transaktionen in diesem 30-Tage-Zeitraum zu gruppieren Geben Sie die Daten so zurück, dass ich sie dann abfragen kann. Nach vielen Tests erreicht dieser Code viel von dem, was ich will:
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;
Und ich werde in einer größeren Abfrage so etwas strukturiert verwenden:
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;
Der Fall, den diese Abfrage nicht abdeckt, liegt vor, wenn die Transaktionszählung mehrere Monate umfassen würde, sich aber immer noch innerhalb von 30 Tagen voneinander befinden würde. Ist diese Art der Abfrage mit Postgres möglich? Wenn ja, begrüße ich jede Eingabe. Viele der anderen Themen behandeln das " Laufen " von Aggregaten, nicht das Rollen .
Aktualisieren
Das CREATE TABLE
Drehbuch:
CREATE TABLE transactiondb (
id integer NOT NULL,
trans_ref_no character varying(255),
amount numeric(18,2),
trans_date date,
entity_id integer
);
Beispieldaten finden Sie hier . Ich verwende PostgreSQL 9.1.16.
Ideal Ausgang würde SUM(amount)
und COUNT()
alle Transaktionen über einen zusammenhängenden Zeitraum von 30 Tagen. Siehe dieses Bild zum Beispiel:
Die grüne Datumsmarkierung zeigt an, was in meiner Abfrage enthalten ist. Die gelbe hervorgehobene Zeile gibt Aufzeichnungen darüber an, was ich Teil des Sets werden möchte.
Vorherige Lektüre:
quelle
every possible 30-day period by entity_id
Sie kann bedeuten , beginnt die Frist für jeden Tag, also 365 mögliche Perioden in einem (nicht-Sprung) Jahr? Oder möchten Sie Tage mit einer tatsächlichen Transaktion nur einzeln als Beginn einer Periode betrachtenentity_id
? In beiden Fällen geben Sie bitte Ihre Tabellendefinition, die Postgres-Version, einige Beispieldaten und das erwartete Ergebnis für das Beispiel an.entity_id
in einem 30-Tage-Fenster Zeilen desselben akkumulieren . Kann es mehrere Transaktionen für dieselbe geben oder ist diese Kombination eindeutig definiert? Ihre Tabellendefinition hat keine oder keine PK-Einschränkung, aber Einschränkungen scheinen zu fehlen ...(trans_date, entity_id)
UNIQUE
id
Primärschlüssel. Pro Unternehmen und Tag können mehrere Transaktionen durchgeführt werden.Antworten:
Die Frage, die Sie haben
Sie könnten Ihre Abfrage mit einer
WINDOW
Klausel vereinfachen , dies verkürzt jedoch nur die Syntax, ohne den Abfrageplan zu ändern.count(*)
, daid
ist sicher definiertNOT NULL
?ORDER BY entity_id
du schonPARTITION BY entity_id
Sie können jedoch noch weiter vereinfachen:
Fügen Sie
ORDER BY
der Fensterdefinition überhaupt nichts hinzu , da dies für Ihre Abfrage nicht relevant ist. Dann müssen Sie auch keinen benutzerdefinierten Fensterrahmen definieren:Einfacher, schneller, aber immer noch eine bessere Version von dem, was Sie haben , mit statischen Monaten.
Die Abfrage, die Sie möchten
... ist nicht klar definiert, daher werde ich auf diesen Annahmen aufbauen:
Zählen Sie Transaktionen und Beträge für jeden 30-Tage-Zeitraum innerhalb der ersten und letzten Transaktion
entity_id
. Schließen Sie führende und nachfolgende Perioden ohne Aktivität aus, schließen Sie jedoch alle möglichen 30-Tage-Perioden innerhalb dieser äußeren Grenzen ein.In dieser Liste sind alle 30-Tage-Zeiträume
entity_id
mit Ihren Aggregaten undtrans_date
als erster Tag (einschließlich) des Zeitraums aufgeführt. Um Werte für jede einzelne Zeile zu erhalten, verbinden Sie die Basistabelle erneut ...Die grundlegende Schwierigkeit ist die gleiche wie hier beschrieben:
Die Frame-Definition eines Fensters kann nicht von den Werten der aktuellen Zeile abhängen.
Und lieber
generate_series()
mittimestamp
Eingabe aufrufen :Die Abfrage, die Sie tatsächlich möchten
Nach Aktualisierung und Diskussion der Fragen:
Sammeln Sie Zeilen derselben
entity_id
in einem 30-Tage-Fenster, beginnend mit jeder tatsächlichen Transaktion.Da Ihre Daten nur spärlich verteilt sind, sollte es effizienter sein, einen Self-Join mit einer Bereichsbedingung auszuführen , zumal Postgres 9.1 noch keine
LATERAL
Joins hat:SQL-Geige.
Ein rollendes Fenster kann nur für die meisten Tage (in Bezug auf die Leistung) mit Daten sinnvoll sein.
Dies gilt nicht Aggregat Duplikate auf
(trans_date, entity_id)
pro Tag, aber alle Zeilen des gleichen Tages werden immer in den 30-Tage - Fenstern enthalten.Für einen großen Tisch könnte ein Abdeckungsindex wie dieser einiges helfen:
Die letzte Spalte
amount
ist nur nützlich, wenn Sie Index-Scans erhalten. Sonst lass es fallen.Es wird jedoch nicht verwendet, wenn Sie die gesamte Tabelle auswählen. Es würde Abfragen für eine kleine Teilmenge unterstützen.
quelle
column "t0.amount" must appear in the GROUP BY clause...