计算每日最低余额

发布于 2024-10-09 19:39:31 字数 1059 浏览 2 评论 0原文

假设我有一个名为 transaction 的表,其中包含 transaction_date、存款、取款字段。一天可能有也可能没有交易,但每天可以有多个交易。所以,我需要做的是给定一个日期范围,比如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

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

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

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

Let's say I have a table named transaction with transaction_date, deposit, withdrawal fields. There may or may not be transaction on a day but can have multiple transactions for each day. So, what I need to do is given a date range, say December 1, 2010 to December 31, 2010, I need to figure out the minimum balance on each day. Let's assume there are transactions before December 1, 2010 as well. Is there anyone who can give me an idea on this one?

Thank you.

Update With Example

 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

In above example, the minimum daily balance from Dec 1 to Dec 10 would be 50. On Dec 10 there are two deposits totaling 70 but the minimum balance on that day would be 50 (carried over from previous day).

Now lets look at multiple transactions.

The carried over on Dec 20 is 200. The first deposit makes it 250, the second one makes it 180, the third one makes it 230 and the last transaction makes it 280. So, the minimum balance for that day would be 180 after withdrawing 70 on the second transaction of the day. Is it possible to generate this using a query on PostgreSQL 8.4 or should I use another approach?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

枯叶蝶 2024-10-16 19:39:31

编辑2
这是一个完整的示例,包括前一天的(最小)余额(据我所知,用这么小的一组数据)。它应该在 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;

Edit2
This is a full example, including the (minimum) balance from the previous day (as far as I can tell with such a small set of data). It should run on 8.4.

I refactored the derived tables to use CTE (common table expression) to make it (hopefully) a bit more readable:

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;
红颜悴 2024-10-16 19:39:31

忽略其他答案中的所有内容。马伏里奥那个家伙是个吹牛者和白痴。试试这个:

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 的等效项。

Ignore everything in that other answer. That guy Malvolio is an blowhard and an idiot. Try this instead:

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;

I was just about to fall asleep when this occurred to me. The IFNULL may be a MySQL specific thing, but you can find a Postgres equivalent.

十雾 2024-10-16 19:39:31

我假设您所说的最低余额是一天开始时或一天结束时的余额较少?

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

前一天的余额:(

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

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

然后:

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

然后以较大者为准。

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

I'm assuming by minimum balance that you're talking about which you have less at, the start or the end of the day?

I suppose for each day you'd do something like this:

Balance from day before:

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

(not sure how date comparison would be done in PostgreSQL

Then:

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

Then whichever is greater.

If that's not what you meant, we need more info.

旧城空念 2024-10-16 19:39:31

首先,我假设交易是按顺序编号的。根据定义,交易必须正确排序(因为在同一天存款 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;

我无法在这里测试它,但它应该可以工作。

First, I'm going to assume that the transactions are sequentially numbered. Just by definition, the transactions have to be properly ordered (because a $50 deposit followed by a $50 withdrawal on the same day would produce a very different answer from the same steps in a different order) and numbering them sequentially makes other things much easier. Then we have to do some procedural handwaving:

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;

(Forgive any typos -- I don't have PostGres handy). Now we have a table with all the balances in it, we just have to dig it out.

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;

I can't test this here, but it should work.

白鸥掠海 2024-10-16 19:39:31

假设您对一天内的交易进行编号,我采用以下模式:

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 子句早期版本不支持。如果您无法升级,您可能需要某种程序解决方案,就像其他答案所建议的那样。

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

Assuming you number your transactions within a day, I took the following schema:

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);

Then, the following query appears to give you your answer:

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;    

Note, however, that you need PostgreSQL 9.0 for that because the 1 PRECEDING clause is not supported in earlier versions. If you can't upgrade, you will probably need some kind of procedural solution like the other answers suggest.

In any case I recommend writing unit tests for this. ;-)

陈年往事 2024-10-16 19:39:31

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

Why don't you add a column to the database that tracks the current balance (calculated at each deposit/withdrawal). That way it would simply a case of returning the minimum for that column within the date range you're interested in.

独孤求败 2024-10-16 19:39:31

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

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

再次感谢您的所有帮助。

Thank you everyone for the help. I've used the following to solve this. I don't know how efficient the code is though.

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

Again, thanks for all your help.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文