Sha*_*vil 9 postgresql group-by
我balances
在 PostgreSQL 9.3 中有一个表,如下所示:
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');
Run Code Online (Sandbox Code Playgroud)
它只包含用户进行交易的日期的余额。我需要它为每个用户包含一行以及给定日期范围内每个日期的余额。
我可以引用一个accounts
表来获取用户的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');
Run Code Online (Sandbox Code Playgroud)
我想要的结果是这样的:
+---------+---------+--------------------------+
| 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 |
+---------+---------+--------------------------+
Run Code Online (Sandbox Code Playgroud)
请注意,已经为用户 2 添加了行2016-01-02
和2016-01-03
,结转了之前的余额2016-01-01
;并且没有为在 上创建的用户 3 添加任何行2016-01-03
。
要在日期范围内生成一系列日期,我知道我可以使用:
SELECT d.date FROM GENERATE_SERIES('2016-01-01', '2016-01-03', '1 day'::INTERVAL) d
Run Code Online (Sandbox Code Playgroud)
...但我正在努力用LEFT JOIN
每组由user_id
.
CROSS JOIN
、LEFT JOIN LATERAL
子查询SELECT a.user_id, COALESCE(b.balance, 0) AS balance, d.as_of_date
FROM (
SELECT d::date AS as_of_date -- cast to date right away
FROM generate_series(timestamp '2016-01-01', '2016-01-03', interval '1 day') d
) d
JOIN accounts a ON a.create_date <= d.as_of_date
LEFT JOIN LATERAL (
SELECT balance
FROM balances
WHERE user_id = a.user_id
AND as_of_date <= d.as_of_date
ORDER BY as_of_date DESC
LIMIT 1
) b ON true
ORDER BY a.user_id, d.as_of_date;
Run Code Online (Sandbox Code Playgroud)
返回您想要的结果 - 除了在您的示例中as_of_date
是一个实际的date
,而不是timestamp
类似的。那应该更合适。
已创建但尚未进行任何交易的用户以余额为 0 列出。您没有定义如何处理极端情况。
而是使用timestamp
输入generate_series()
:
使用多列索引对此进行备份对性能至关重要:
CREATE INDEX balances_multi_idx ON balances (user_id, as_of_date DESC, balance);
Run Code Online (Sandbox Code Playgroud)
就在本周,我们在 SO 上遇到了一个非常相似的案例:
在那里找到更多解释。
CROSS JOIN
, LEFT JOIN
, 窗口函数SELECT user_id
, COALESCE(max(balance) OVER (PARTITION BY user_id, grp
ORDER BY as_of_date), 0) AS balance
, as_of_date
FROM (
SELECT a.user_id, b.balance, d.as_of_date
, count(b.user_id) OVER (PARTITION BY user_id ORDER BY as_of_date) AS grp
FROM (
SELECT d::date AS as_of_date -- cast to date right away
FROM generate_series(timestamp '2016-01-01', '2016-01-03', interval '1 day') d
) d
JOIN accounts a ON a.create_date <= d.as_of_date
LEFT JOIN balances b USING (user_id, as_of_date)
) sub
ORDER BY user_id, as_of_date;
Run Code Online (Sandbox Code Playgroud)
结果一样。如果您有上面提到的多列索引并且可以从中获取仅索引扫描,那么第一个解决方案很可能更快。
主要特征是形成组的值的运行计数。由于 count() 不计算 NULL 值,因此所有没有余额的日期与grp
最近的余额属于同一组 ( )。然后max()
在相同的窗口框架上使用一个简单的扩展grp
来复制悬空间隙的最后一个平衡。
有关的:
归档时间: |
|
查看次数: |
10525 次 |
最近记录: |