【问题标题】: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 没有人收到您的评论通知?偶然发现的。

标签: sql postgresql window-functions


【解决方案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 没有行。相关答案以及自定义框架的更多解释:

  • 在最终的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 中的每一行是个好主意。少一个窗口函数的子查询。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-11-04
    • 1970-01-01
    • 1970-01-01
    • 2022-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多