从余额行中递归减去存款

Posted

技术标签:

【中文标题】从余额行中递归减去存款【英文标题】:Recursive subtraction of deposit from balance rows 【发布时间】:2014-11-26 07:39:16 【问题描述】:

考虑以下 2 个表格:

表 A:

PIN | ENCOUNTER | BALANCE | REFERENCE_DATE 
------------------------------------------
P1  | ABC       | 100     | 11-19-2014     
P1  | HJI       | 300     | 11-20-2014     
P1  | PIY       | 700     | 11-21-2014     
P2  | CDO       | 200     | 11-20-2014     
P2  | NHG       | 200     | 11-21-2014    
P3  | CVB       | 500     | 11-20-2014
P3  | SJK       | 100     | 11-21-2014     

表 B:

PIN | DEPOSIT
-------------
P1  | 1000
P2  | 400
P3  | 100

最初表BDEPOSIT值将从表A中的BALANCE中减去,最早的REFERENCE_DATEPIN匹配。如果差值大于 0,则会从下一行的 BALANCE 中减去,直到剩余的 DEPOSIT 小于或等于 0。

从余额中减去存款后的结果如下所示。我已经包括了另一列,其中存款是按遭遇划分的:

PIN | ENCOUNTER | BALANCE | REFERENCE_DATE | DEPOSITS_BREAKDOWN
---------------------------------------------------------------
P1  | ABC       | 0       | 11-19-2014     | 100
P1  | HJI       | 0       | 11-20-2014     | 300
P1  | PIY       | 100     | 11-21-2014     | 600
P2  | CDO       | 0       | 11-20-2014     | 200
P2  | NHG       | 0       | 11-21-2014     | 200
P3  | CVB       | 400     | 11-20-2014     | 100
P3  | SJK       | 100     | 11-21-2014     | 0

我的 Postgres 版本是 9.3。我正在努力为这个制定查询。

【问题讨论】:

最后的结果行应该是 500,而不是 100。 @ErwinBrandstetter,如果是这样,那我就浪费了 30 分钟。这个100有点难对付。 其实最后一行真的是100,分配给A表P3行的100押金已经减去了遇到CVB的那一行。 非常感谢先生。但是是否也可以在另一列中列出存款被分割了多少?我已经更新了结果表。 是的,这很便宜。另外:您是否知道没有@-reply 没有人收到您的评论通知?偶然发现的。 【参考方案1】:

DEPOSIT 覆盖BALANCE 时设置为0

正如您所澄清的,您不想要 BALANCE 的运行总和,只需设置为 0 直到 DEPOSIT 用完:

SELECT PIN, ENCOUNTER
     , CASE WHEN last_sum >= DEPOSIT THEN BALANCE
            ELSE GREATEST (last_sum + BALANCE - DEPOSIT, 0) END AS BALANCE
     , REFERENCE_DATE
     , CASE WHEN last_sum >= DEPOSIT THEN 0
            ELSE LEAST (BALANCE, DEPOSIT - last_sum) END AS DEPOSITS_BREAKDOWN
FROM (
   SELECT a.*
        , COALESCE(sum(a.BALANCE) OVER (
                         PARTITION BY PIN ORDER BY a.REFERENCE_DATE
                         ROWS BETWEEN UNBOUNDED PRECEDING
                                          AND 1 PRECEDING), 0) AS last_sum
        , COALESCE(b.DEPOSIT, 0) AS DEPOSIT
   FROM        table_a a
   LEFT   JOIN table_b b USING (pin)
   ) sub;

准确返回您想要的结果。

SQL Fiddle.

我采纳了@vyegorov 所评论的更简单连接的想法。

LEFT JOINtable_b - 这样就有可能在 table_b 中找不到任何行。

在子查询中,计算 BALANCE 的运行总和,直到最后一行 (last_sum)。为此在窗口函数中使用自定义框架。并且 COALESCE 默认为 0 没有行。相关答案以及自定义框架的更多解释:

How to use a ring data structure in window functions Querying count on daily basis with date constraints over multiple weeks

在最终的SELECT 中,如果last_sum 等于或大于DEPOSIT(已用完),则返回原始BALANCE。 ELSE 返回剩余差或 0 是 BALANCE (last_sum + BALANCE) 的运行总和小于 DEPOSIT

运行总和

上一个(更简单的)答案以 BALANCE 作为运行总和(最后一行 500 而不是 100):

SELECT a.PIN, a.ENCOUNTER
     , GREATEST(sum(a.BALANCE) OVER (PARTITION BY PIN ORDER BY a.REFERENCE_DATE) 
                 - COALESCE(b.DEPOSIT, 0), 0) AS BALANCE
     , a.REFERENCE_DATE
FROM   table_a      a
LEFT   JOIN table_b b USING (pin);

【讨论】:

【参考方案2】:

我想出了这个查询:

SELECT *,
       sum(balance) OVER w                          balance_accum,
       greatest(deposit - sum(balance) OVER w, 0)   deposit_new,
       greatest(sum(balance) OVER w - deposit, 0)   balance_new
  FROM table_a JOIN table_b USING(pin)
WINDOW w AS (PARTITION BY pin ORDER BY reference_date)
 ORDER BY pin, reference_date;

SQL Fiddle

正如 Erwin 提到的,这一行假设最后一行包含 500 而不是 100


编辑

这个查询会产生所需的输出:

SELECT s.*,
       CASE WHEN min(deposit_new) OVER w = 0 THEN 0 
            ELSE least(min(deposit_new) OVER w, deposit_diff) END     deposit_used,
       balance -
         CASE WHEN min(deposit_new) OVER w = 0 THEN 0
              ELSE least(min(deposit_new) OVER w, deposit_diff) END   balance_real
  FROM
  (
    SELECT *,
           sum(balance) OVER w                                      balance_accum,
           greatest(coalesce(deposit,0) - sum(balance) OVER w, 0)   deposit_new,
           least(balance, coalesce(deposit,0))                      deposit_diff
      FROM table_a LEFT JOIN table_b USING(pin)
    WINDOW w AS (PARTITION BY pin ORDER BY reference_date)
  ) s
WINDOW w AS (PARTITION BY pin ORDER BY reference_date
             ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)
 ORDER BY pin, reference_date;

这里有什么(子查询):

正如 Erwin 建议的那样,LEFT JOINcoalesce(deposit,0) 用于保存没有存款的条目; 实际balance 值的总和计算并从deposit 中减去(输出中的列deposit_new); deposit_diffbalancedeposit 中的最小值,用于调整外部平衡。

在外面:

检查最小deposit_new 值,如果达到0,则跳过所有进一步的“使用”; 否则至少取deposit_newdeposit_diff 的值; 检查组中所有前面的行。

子查询是必需的,因为我必须在逻辑中使用窗口函数的结果。

SQL Fiddle 2

【讨论】:

加入 table_a 中的每一行是个好主意。少一个窗口函数的子查询。

以上是关于从余额行中递归减去存款的主要内容,如果未能解决你的问题,请参考以下文章

SAP银行存款余额怎么查

编写一个类似银行账户的程序,属性:账号 储户姓名 地址 存款余额 利率。方法:存款 取款查询余额计算利息

JAVA编写模拟ATM机进行帐户余额查询 实现存款和取款业务(使用带参数的方法)

从 2 个表中减去总计

首先定义一个描述银行账户的Account类,包括成员变 量“账号”和“存款余额”,成员方法有“存款”“取款”和“余额查询”。其次, 编写一个主类,在主类中测试Account类的功能。

RecursionError:超出最大递归深度