Ich habe eine balances
Tabelle in PostgreSQL 9.3, die so aussieht:
CREATE TABLE balances (
user_id INT
, balance INT
, as_of_date DATE
);
INSERT INTO balances (user_id, balance, as_of_date) VALUES
(1, 100, '2016-01-03')
, (1, 50, '2016-01-02')
, (1, 10, '2016-01-01')
, (2, 200, '2016-01-01')
, (3, 30, '2016-01-03');
Es enthält nur Salden für Daten, an denen ein Benutzer eine Transaktion durchgeführt hat. Ich brauche es, um eine Zeile für jeden Benutzer mit ihrem Kontostand an jedem Datum in einem bestimmten Datumsbereich zu enthalten.
- Wenn der Benutzer für ein bestimmtes Datum keine Zeile im Bereich hat, muss ich sein Guthaben vom Vortag verwenden.
- Wenn der Benutzer sein Konto nach einem bestimmten Datum im Bereich erstellt hat, muss vermieden werden, dass eine Zeile für diese Benutzer- / Datumskombination erstellt wird.
Ich kann auf eine accounts
Tabelle verweisen , um Benutzer zu erhalten create_date
:
CREATE TABLE accounts (
user_id INT
, create_date DATE
);
INSERT INTO accounts (user_id, create_date) VALUES
(1, '2015-12-01')
, (2, '2015-12-31')
, (3, '2016-01-03');
Mein gewünschtes Ergebnis sieht so aus:
+---------+---------+--------------------------+
| user_id | balance | as_of_date |
+---------+---------+--------------------------+
| 1 | 100 | 2016-01-03T00:00:00.000Z |
| 1 | 50 | 2016-01-02T00:00:00.000Z |
| 1 | 10 | 2016-01-01T00:00:00.000Z |
| 2 | 200 | 2016-01-03T00:00:00.000Z |
| 2 | 200 | 2016-01-02T00:00:00.000Z |
| 2 | 200 | 2016-01-01T00:00:00.000Z |
| 3 | 30 | 2016-01-03T00:00:00.000Z |
+---------+---------+--------------------------+
Beachten Sie, dass für Benutzer 2 Zeilen für 2016-01-02
und hinzugefügt wurden 2016-01-03
, die den vorherigen Saldo von übertragen 2016-01-01
. und dass für Benutzer 3, der am erstellt wurde, keine Zeilen hinzugefügt wurden 2016-01-03
.
Um eine Reihe von Daten in einem Datumsbereich zu generieren, kann ich Folgendes verwenden:
SELECT d.date FROM GENERATE_SERIES('2016-01-01', '2016-01-03', '1 day'::INTERVAL) d
... aber ich habe LEFT JOIN
Probleme damit, diese Serie mit jedem Satz von Zeilen zu gruppieren, die nach gruppiert sind user_id
.
quelle
created_at
? Listen Sie sie mit Saldo 0 für diese ersten Tage auf? Oder mit NULL? Oder erst bei der ersten Transaktion auflisten? Oder nicht möglich?Antworten:
1.
CROSS JOIN
,LEFT JOIN LATERAL
zu subqueryGibt das gewünschte Ergebnis zurück - außer dass dies
as_of_date
ein tatsächlichesdate
und keintimestamp
ähnliches Ergebnis in Ihrem Beispiel ist. Das sollte angemessener sein.Benutzer, die bereits erstellt wurden, aber noch keine Transaktionen haben, werden mit einem Saldo von 0 aufgelistet. Sie haben nicht definiert, wie mit dem Eckfall umgegangen werden soll.
Verwenden Sie die
timestamp
Eingabe lieber fürgenerate_series()
:Für die Leistung ist es entscheidend, dass Sie dies mit einem mehrspaltigen Index sichern:
Wir hatten gerade diese Woche einen sehr ähnlichen Fall bei SO:
Weitere Erklärungen finden Sie dort.
2.
CROSS JOIN
,LEFT JOIN
, FensterfunktionenGleiches Ergebnis. Wenn Sie den oben genannten mehrspaltigen Index haben und nur Index-Scans daraus erhalten können, ist die erste Lösung höchstwahrscheinlich schneller.
Das Hauptmerkmal ist die laufende Anzahl von Werten, um Gruppen zu bilden. Da count () keine NULL-Werte zählt, fallen alle Daten ohne Kontostand in dieselbe Gruppe (
grp
) wie der letzte Kontostand. Verwenden Sie dann einen einfachen,max()
über denselben Fensterrahmen verlängerten Rahmen, umgrp
die letzte Waage für baumelnde Lücken zu kopieren.Verbunden:
quelle
Wenn das Gleichgewicht monoton ist, erhöht sich etwas wie:
sollte tun. Das Problem ist für den allgemeinen Fall wahrscheinlich etwas einfacher, wenn Sie anstelle der Salden pro Datum Zugriff auf die einzelnen Transaktionen haben.
quelle