计算每日最低余额

时间:2010-12-31 06:25:29

标签: java postgresql

假设我有一个名为transaction的表,其中包含transaction_date,deposit,withdraw字段。一天可能有也可能没有交易,但每天可以有多笔交易。所以,我需要做的是给出一个日期范围,比如说2010年12月1日到2010年12月31日,我需要计算出每天的最低余额。我们假设在2010年12月1日之前也有交易。是否有人可以就此问题给我一个想法?

谢谢。

更新 使用示例

 tran_date   withdraw    deposit
2010-11-23       0.00      50.00
2010-12-10       0.00      50.00
2010-12-10       0.00     200.00
2010-12-12     100.00       0.00
2010-12-20       0.00      50.00
2010-12-20      70.00       0.00
2010-12-20       0.00      50.00
2010-12-20       0.00      50.00
2010-12-24     150.00       0.00

在上面的示例中,从 Dec 1 Dec 10 的最低每日余额将 50 。在 12月10日,有两个存款总计 70 ,但当天的最低余额将 50 (从前一天结转)。

现在让我们看看多个交易。

Dec 20 的结转 200 。第一笔存款使其 250 ,第二笔存款使其 180 ,第三笔存款使其 230 ,最后一笔交易使其 280 即可。因此,在当天的第二笔交易中撤销 70 后,当天的最低余额将 180 。是否可以使用PostgreSQL 8.4上的查询生成它,还是应该使用其他方法?

7 个答案:

答案 0 :(得分:2)

<强> EDIT2
这是一个完整的例子,包括前一天的(最小)余额(据我所知,这么小的一组数据)。它应该在8.4上运行。

我重构派生表以使用CTE(公用表表达式)使它(希望)更具可读性:

WITH days AS (
   -- generate a liste of possible dates spanning 
   -- the whole interval of the transactions
   SELECT min(tran_date) + generate_series(0, max(tran_date) - min(tran_date)) AS some_date
   FROM transaction
),
total_balance AS (
  -- Calculate the running totals for all transactions
  SELECT tran_id,
         days.some_date as tran_date, 
         deposit, 
         withdrawal,
         sum(deposit - withdrawal) 
             OVER (ORDER BY some_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as balance
  FROM days
   LEFT JOIN transaction t ON t.tran_date = days.some_date 
),
min_balance AS (
  -- calculate the minimum balance for each day 
  -- (the smalles balance will have a '1' in the column balance_rank)
  SELECT tran_id, 
         tran_date,
         rank() OVER (PARTITION BY tran_date ORDER BY balance) as balance_rank,
         balance
  FROM total_balance
)
-- Now get everything, including the balance for the previous day
SELECT tran_id,
       tran_date,
       balance,
       lag(balance) over (order by tran_date) as previous_balance
FROM min_balance
WHERE balance_rank = 1;

答案 1 :(得分:2)

忽略其他答案中的所有内容。那个家伙Malvolio是个傻瓜,也是个白痴。试试这个:

SELECT MIN(balance), transaction_date FROM
( SELECT a.transaction_date, IFNULL(sum(b.deposit) - sum(b.withdrawal), 0) balance FROM transaction a
        LEFT JOIN transaction b ON a.seqno > b.seqno GROUP ON a.seqno
   UNION
  SELECT a.transaction_date, IFNULL(sum(b.deposit) - sum(b.withdrawal), 0) balance FROM transaction a
        LEFT JOIN transaction b ON a.seqno >= b.seqno GROUP ON a.seqno  ) x
GROUP BY transaction_date;

当我发生这种情况时,我正要睡着了。 IFNULL可能是MySQL特有的东西,但你可以找到Postgres等价物。

答案 2 :(得分:0)

我假设你说的最低限度是你在一天的开始或结束时少开的?

我想每天你都会做这样的事情:

前一天的平衡:

SELECT (SUM(deposit) - SUM(withdrawal)) WHERE date < [date you're after]

(不确定如何在PostgreSQL中进行日期比较

然后:

SELECT (SUM(deposit) - SUM(withdrawal)) WHERE date = [date you're after]

然后以较大者为准。

如果那不是您的意思,我们需要更多信息。

答案 3 :(得分:0)

首先,我假设交易是按顺序编号。根据定义,交易必须正确订购(因为50美元的存款,然后在同一天提取50美元,将以不同的顺序从相同的步骤产生非常不同的答案),并按顺序编号使其他事情变得更容易。然后我们必须做一些程序性的手工操作:

CREATE TABLE running_total (seqno INT, transaction_date DATE, before NUMBER(10,20), after NUMBER(10,20);
SET tot=0;
FOR transaction IN SELECT * FROM transaction ORDER BY seqno ASC LOOP
    SET oldtot = tot;
    SET tot = tot = transaction.deposit - transaction.withdrawal;
    EXECUTE 'INSERT INTO running_total (seqno, transaction_date, before, after) VALUES (' ||
    transaction.seqno || ', ' || transaction.transaction_date || ',' || oldtot || ',' || tot || ')';
END LOOP;

(原谅任何错别字 - 我没有PostGres方便)。现在我们有一张包含所有余额的表格,我们只需要挖掘它。

SELECT MIN(balance), transaction_date FROM
( SELECT before as balance, transaction_date FROM running_total
   UNION
 SELECT after as balance, transaction_date FROM running_total) x 
GROUP BY transaction_date;

我不能在这里测试,但它应该有用。

答案 4 :(得分:0)

假设您在一天内为您的交易编号,我采用了以下架构:

CREATE TABLE transaction (
    tran_date date,
    num       int,
    withdraw  numeric,
    deposit   numeric
);

INSERT INTO transaction VALUES
    ('2010-11-23', 1,      0.00,      50.00),
    ('2010-12-10', 1,      0.00,      50.00),
    ('2010-12-10', 2,      0.00,     200.00),
    ('2010-12-12', 1,    100.00,       0.00),
    ('2010-12-20', 1,      0.00,      50.00),
    ('2010-12-20', 2,     70.00,       0.00),
    ('2010-12-20', 3,      0.00,      50.00),
    ('2010-12-20', 4,      0.00,      50.00),
    ('2010-12-24', 1,    150.00,       0.00);

然后,以下查询会显示您的答案:

WITH dates (tran_date) AS (SELECT date '2010-12-01' + generate_series(0, 30)),    
     transactions AS (SELECT tran_date, num,
                             coalesce(withdraw, 0) AS withdraw, 
                             coalesce(deposit, 0) AS deposit
                      FROM dates FULL OUTER JOIN transaction USING (tran_date)),
     running_totals AS (SELECT tran_date,     
                               sum(deposit - withdraw) OVER (ORDER BY tran_date, num ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS lagging_total,
                               sum(deposit - withdraw) OVER (ORDER BY tran_date, num ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS current_total
                        FROM transactions)
SELECT tran_date, min(least(lagging_total, current_total))
FROM running_totals
GROUP BY tran_date
HAVING tran_date IN (SELECT tran_date FROM dates)
ORDER BY tran_date;    

但请注意,您需要PostgreSQL 9.0,因为早期版本不支持1 PRECEDING子句。如果你无法升级,你可能需要某种程序解决方案,如其他答案所示。

无论如何,我建议为此编写单元测试。 ; - )

答案 5 :(得分:0)

为什么不在跟踪当前余额的数据库中添加一列(在每次存款/取款时计算)。这样,只需要在您感兴趣的日期范围内返回该列的最小值。

答案 6 :(得分:0)

谢谢大家的帮助。我用以下方法来解决这个问题。我不知道代码的效率如何。

select dt::date, 
coalesce(case when balance<=coAmt then balance else coAmt end, 
(select sum(coalesce(deposit, 0.00))-sum(coalesce(withdraw, 0.00)) 
from  where tran_date<=dt::date and acc_no='3'), 0.00) amt
from (
select tran_date, min(balance) balance, 
coalesce((select sum(coalesce(deposit, 0.00) - coalesce(withdraw, 0.00)) 
from transaction where tran_date<t.tran_date and acc_no=t.acc_no), 0.00) coAmt
from (
select tran_id, acc_no, tran_date, deposit, withdraw,
sum(deposite - withdraw) over (order by tran_id) balance 
from transaction sv group by tran_id, acc_no, tran_date, deposite, withdraw) t 
where acc_no='3' group by tran_date, acc_no order by tran_date ) t1 
right join 
generate_series('2010-12-01', '2010-12-31', interval '1 day') as dt on dt=tran_date 
group by dt, tran_date, balance, coAmt order by dt

再次感谢您的帮助。