简体   繁体   English

如何使用 PostgreSQL window 函数计算运行余额?

[英]How to calculate running balance using PostgreSQL window functions?

I want a query to track interest on a mortgage account.我想要一个查询来跟踪抵押账户的利息。 For simplicity assume the interest is calculated yearly.为简单起见,假设利息是每年计算的。 There are also one-off deposits/withdrawals (repayments etc).还有一次性存款/取款(还款等)。

I want to query this information and calculate a running balance, presumably using window functions.我想查询此信息并计算运行余额,大概使用 window 函数。 Here is an example of the kind of table I want to query.这是我要查询的那种表的示例。

year | changes | interest | comment
2020 | 10000   | 2.5      | initial mortgage of 10k
2021 | 0       | 2.0      | next year the rate drops
2022 | 5000    | 2.0      | we borrow an extra 5k
2023 | 0.      | 1.5      | rate drop again

I want a query that calculates the running balance each year, like so:我想要一个计算每年运行余额的查询,如下所示:

year | changes | interest | balance
2020 | 10000   | 2.5      | 10250.0 = 10000 * (1 + 2.5 / 100)
2021 | 0       | 2.0      | 10455.0 = 10250 * (1 + 2.0 / 100)
2022 | 5000    | 2.0      | 15764.1 = (10455 + 5000) * (1 + 2.0 / 100)
2023 | 0.      | 1.5      | 16000.56 = 15764.1 * (1 + 1.5 / 100)

How to do this in PostgreSQL?如何在 PostgreSQL 中执行此操作?

Because of the need to multiply the prior year balance by the current interest, this is most easily achieved using a recursive CTE:由于需要将上一年余额乘以当前利息,因此使用递归 CTE 最容易实现:

WITH RECURSIVE CTE AS (
  SELECT t.year, t.changes, t.interest, t.changes * (1.0 + t.interest / 100.0) AS balance
  FROM transactions t
  WHERE year = (SELECT MIN(year) FROM transactions)
  UNION ALL
  SELECT t.year, t.changes, t.interest, (t.changes + CTE.balance) * (1.0 + t.interest / 100.0)
  FROM transactions t
  JOIN CTE ON t.year = CTE.year + 1
)
SELECT year, changes, interest, ROUND(CAST(balance AS numeric), 2) AS balance
FROM CTE

Output: Output:

year    changes     interest    balance
2020    10000       2.5         10250.00
2021    0           2           10455.00
2022    5000        2           15764.10
2023    0           1.5         16000.56

Demo on dbfiddle dbfiddle 上的演示

The recursive CTE is quite possibly the better approach.递归 CTE 很可能是更好的方法。 But it is possible to do this using window functions.但是可以使用 window 函数来做到这一点。

The three key ideas are:三个关键思想是:

  • Using exp(sum(ln())) as the product() aggregation function.使用exp(sum(ln()))作为product()聚合 function。
  • Projecting each value to the latest time, accumulating all interest functions.将每个值投影到最近的时间,累加所有兴趣函数。
  • Dividing by the "accumulated interest" up to that value to adjust for new values entering in.除以该值的“累积利息”以调整输入的新值。

The actual code is not that complicated:实际的代码并不复杂:

select t.*,
        (sum(changes * running_interest) over (order by year) /
         coalesce(prev_running_interest, 1)
        ) as val
from (select t.*, 
             exp(sum(ln(1 + interest / 100)) over (order by year desc)) as running_interest,
             exp(sum(ln(1 + interest / 100)) over (order by year desc rows between unbounded preceding and 1 preceding)) as prev_running_interest
      from t
     ) t
order by year;

You will notice in the db<>fiddle the slight inaccuracies caused by floating point arithmetic.您会在db<>fiddle中注意到浮点运算引起的轻微误差。 You can always cast to fewer decimal places for more aesthetically appealing numbers.您始终可以减少小数位数以获得更具美感的数字。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM