Postgres每月余额

时间:2016-10-26 03:19:32

标签: postgresql window-functions generate-series

我正在使用postgreSQL 9.2 我有一个表,其中包含影响帐户启动余额的交易,我想创建一个生成月度余额的查询。看起来很简单,但是我被卡住了,我能找到的最接近的东西可能就是这个Calculate Monthly Recurring Revenue(MRR) result using postgres 因为它使用generate_series,我认为我需要创建一个月份列表来匹配我的帐户和交易。

这是我桌子的DDL:

CREATE TABLE account (
  id integer NOT NULL,
  startbalance numeric(19,2),
  opendate date NOT NULL
);

CREATE TABLE trx (
 id integer NOT NULL,
 accountid integer NOT NULL,
 amount numeric(19,2) NOT NULL,
 transactiondate date NOT NULL
);
ALTER TABLE ONLY account
 ADD CONSTRAINT account_pk PRIMARY KEY (id);

ALTER TABLE ONLY trx
 ADD CONSTRAINT transaction_pk PRIMARY KEY (id);

ALTER TABLE ONLY trx
 ADD CONSTRAINT transaction_accountid_fk FOREIGN KEY (accountid) REFERENCES account(id);

数据:

INSERT INTO account VALUES (1, 200.00, '2016-01-01');
INSERT INTO account VALUES (2, 400.00, '2016-03-02');
INSERT INTO account VALUES (3, 800.00, '2016-01-15');

INSERT INTO trx VALUES (1, 1, -100.00, '2016-01-10');
INSERT INTO trx VALUES (6, 2, -200.00, '2016-03-25');
INSERT INTO trx VALUES (9, 3, -400.00, '2016-02-03');
INSERT INTO trx VALUES (8, 2, -100.00, '2016-09-15');
INSERT INTO trx VALUES (5, 1, -20.00, '2016-06-20');
INSERT INTO trx VALUES (3, 1, -20.00, '2016-03-04');
INSERT INTO trx VALUES (2, 1, -10.00, '2016-02-02');
INSERT INTO trx VALUES (10, 3, -100.00, '2016-04-12');
INSERT INTO trx VALUES (11, 3, -100.00, '2016-04-25');
INSERT INTO trx VALUES (12, 3, -200.00, '2016-04-29');
INSERT INTO trx VALUES (4, 1, -50.00, '2016-06-05');
INSERT INTO trx VALUES (7, 2, -100.00, '2016-08-05');

我已经弄清楚如何使用窗口函数来获取每笔交易的总计:

SELECT 
  acct.id accountid,
  acct.startbalance,
  amount,
  transactiondate,
  acct.startbalance + sum(amount) OVER (PARTITION BY acct.id ORDER BY transactiondate) balance
FROM 
account acct 
LEFT JOIN trx ON trx.accountid = acct.id 
ORDER BY transactiondate DESC

这给了我:

accountid opendate startbalance amount transactiondate balance
2   2016-03-02  400.00  -100.00 2016-09-15  0.00
2   2016-03-02  400.00  -100.00 2016-08-05  100.00
1   2016-01-01  200.00  -20.00  2016-06-20  0.00
1   2016-01-01  200.00  -50.00  2016-06-05  20.00
3   2016-01-15  800.00  -200.00 2016-04-29  0.00
3   2016-01-15  800.00  -100.00 2016-04-25  200.00
3   2016-01-15  800.00  -100.00 2016-04-12  300.00
2   2016-03-02  400.00  -200.00 2016-03-25  200.00
1   2016-01-01  200.00  -20.00  2016-03-04  70.00
3   2016-01-15  800.00  -400.00 2016-02-03  400.00
1   2016-01-01  200.00  -10.00  2016-02-02  90.00
1   2016-01-01  200.00  -100.00 2016-01-10  100.00

但它没有显示没有数据的月份的任何内容。我需要的是更像结果集的东西,它显示每个月的数据范围(例如1/2016 - 6/2016):

MonthOf AcctId  OrigBal Pmt     Balance
2016-06     3   800     0       0
2016-06     2   400     0       200
2016-06     1   200     -70     0
2016-05     3   800     0       0
2016-05     2   400     0       200
2016-05     1   200     0       70
2016-04     3   800     -400    0
2016-04     2   400     0       200
2016-04     1   200     0       70
2016-03     3   800     -400    400
2016-03     2   400     -200    200
2016-03     1   200     -20     70
2016-02     3   800     -400    400
2016-02     2   0       0       0
2016-02     1   200     -10     90
2016-01     3   800     0       800
2016-01     2   0       0       0
2016-01     1   200     -100    100

我尝试了使用generate_series语句加入CTE的示例,但无法获得预期的结果。下面是我试图放入cte的generate_series结果。我无法正确添加余额。我是窗口功能的新手,所以我确定我只是缺少一些东西,或者我只是在错误的道路上工作。

SELECT  
date_trunc('month', dates) + INTERVAL '1 MONTH - 1 SECOND' end_of_month
FROM 
generate_series('2016-01-01'::timestamp, '2016-6-24'::timestamp, '1 month'::interval) dates

2 个答案:

答案 0 :(得分:0)

没有窗口功能(可能有点难看,但似乎有用......):

注意:我忽略了account.opendate,因为它与求和无关。

更新:未出生的帐户在WHILE子句中处理,现在

WITH cal AS ( -- calender table
        SELECT generate_series('2016-01-01'::date, '2016-10-01'::date , '1 month'::interval)::date dt
        )
SELECT
        a.id, a.startbalance
        , c.dt
        , COALESCE(t.cumsum, 0.0)::NUMERIC(19,2) AS cumsum
        , a.startbalance + COALESCE(t.cumsum, 0.0)::NUMERIC(19,2) AS balance
FROM cal c
CROSS JOIN account a
LEFT JOIN LATERAL ( -- aggregating subquery
        SELECT accountid
        , SUM(amount) AS cumsum
        FROM trx
        WHERE transactiondate < c.dt
        GROUP BY accountid
        ) t ON t.accountid = a.id
WHERE a.opendate <= c.dt -- suppress unborn accounts
ORDER BY a.id, c.dt
        ;

更新:我们可以通过普通的老式标量子查询来避免LATERAL

WITH cal AS ( -- calender table
    SELECT generate_series('2016-01-01', '2016-10-01' , '1 month'::interval)::date dt
    )
, xxx AS ( -- grid table
    SELECT a.startbalance , a.id AS accountid , c.dt AS xdt
    FROM cal c -- in fact a cross join ...
    JOIN account a ON a.opendate <= c.dt
    )
SELECT
    x.accountid, x.startbalance, x.xdt
    , (SELECT SUM(t.amount) FROM trx t
        WHERE t.accountid = x.accountid
        AND t.transactiondate <= x.xdt) AS cumsum
    , COALESCE(x.startbalance
             + (SELECT SUM(t.amount) FROM trx t
               WHERE t.accountid = x.accountid
               AND t.transactiondate <= x.xdt)
         , x.startbalance) AS balance
FROM xxx x
ORDER BY x.accountid, x.xdt
        ;

答案 1 :(得分:0)

这给了我可以使用的结果:

WITH dates AS (

    SELECT  
    date_trunc('month', dates) + INTERVAL '1 MONTH - 1 SECOND' end_of_month
    FROM 
    generate_series('2016-01-01'::timestamp, '2016-6-24'::timestamp, '1 month'::interval) dates
)


SELECT 
date_trunc('month', dates.end_of_month) endofmonth,
acct.id accountid,
acct.opendate,
acct.startbalance,
acct.startbalance + (SUM(sum(COALESCE(trx.amount, 0))) OVER (PARTITION BY acct.id ORDER BY dates.end_of_month)) balance
FROM 
dates
LEFT join account acct ON date_trunc('month', dates.end_of_month) >= date_trunc('month',acct.opendate)
LEFT JOIN trx ON trx.accountid = acct.id AND date_trunc('month', trx.transactionDate) = date_trunc('month', dates.end_of_month) 
GROUP BY 
dates.end_of_month,
acct.id,
acct.opendate,
acct.startbalance
ORDER BY endofmonth DESC, acct.id DESC
相关问题