从余额行递归减去存款

时间:2014-11-26 07:39:16

标签: sql postgresql window-functions

考虑以下两个表:

表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

最初,表B的{​​{1}}值将从表DEPOSIT中的BALANCE中减去,A最早与REFERENCE_DATE匹配}}。如果差异大于0,则会从下一行的PIN中减去差异,直到剩余的BALANCE变为小于或等于0。

从余额中减去存款后的结果将如下所示。我已经列入了另一个专栏,其中存款按每次遭遇划分:

DEPOSIT

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

2 个答案:

答案 0 :(得分:2)

设置为0,DEPOSIT涵盖BALANCE

正如您澄清的那样,您不希望运行总和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,其中没有行。相关答案以及自定义框架的更多说明:

  • 在最终SELECT中,如果BALANCE等于或大于last_sum(已花费),则返回原始DEPOSIT。 ELSE返回剩余的差异,或0表示BALANCElast_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);

答案 1 :(得分:1)

我提出了这个问题:

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

正如欧文所提到的,这个假设最后一行包含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_diff至少是balancedeposit,用于调整外部的余额。

在外部:

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

我必须使用子查询才能在逻辑中使用窗口函数的结果。

SQL Fiddle 2