Oracle SQL总结值,直到达到另一个值

PzV*_*ium 8 sql oracle

我希望我能以一种可以理解的方式描述我的挑战.我在Oracle Database 12c上有两个表,如下所示:

表名"发票"

I_ID | invoice_number |     creation_date     | i_amount
------------------------------------------------------
  1  |  10000000000   |  01.02.2016 00:00:00  |   30
  2  |  10000000001   |  01.03.2016 00:00:00  |   25
  3  |  10000000002   |  01.04.2016 00:00:00  |   13
  4  |  10000000003   |  01.05.2016 00:00:00  |   18
  5  |  10000000004   |  01.06.2016 00:00:00  |   12
Run Code Online (Sandbox Code Playgroud)

表名"付款"

P_ID |   reference    |     received_date     | p_amount
------------------------------------------------------
  1  |  PAYMENT01     |  12.02.2016 13:14:12  |   12
  2  |  PAYMENT02     |  12.02.2016 15:24:21  |   28
  3  |  PAYMENT03     |  08.03.2016 23:12:00  |    2
  4  |  PAYMENT04     |  23.03.2016 12:32:13  |   30
  5  |  PAYMENT05     |  12.06.2016 00:00:00  |   15
Run Code Online (Sandbox Code Playgroud)

所以我想要一个select语句(可能有oracle分析函数但我并不熟悉它),其中的付款总结到达到发票金额,按日期排序.如果两笔付款的总和超过发票金额,则最后一笔付款金额的其余部分应用于下一张发票.

在此示例中,结果应如下所示:

invoice_number | reference | used_pay_amount | open_inv_amount
----------------------------------------------------------
 10000000000   | PAYMENT01 |       12        |        18
 10000000000   | PAYMENT02 |       18        |         0
 10000000001   | PAYMENT02 |       10        |        15
 10000000001   | PAYMENT03 |        2        |        13
 10000000001   | PAYMENT04 |       13        |         0
 10000000002   | PAYMENT04 |       13        |         0
 10000000003   | PAYMENT04 |        4        |        14
 10000000003   | PAYMENT05 |       14        |         0
 10000000004   | PAYMENT05 |        1        |        11 
Run Code Online (Sandbox Code Playgroud)

如果有一个带有"简单"select语句的解决方案,那就太好了.

提前为你的时间...

MT0*_*MT0 8

Oracle安装程序:

CREATE TABLE invoices ( i_id, invoice_number, creation_date, i_amount ) AS
SELECT 1, 100000000, DATE '2016-01-01', 30 FROM DUAL UNION ALL
SELECT 2, 100000001, DATE '2016-02-01', 25 FROM DUAL UNION ALL
SELECT 3, 100000002, DATE '2016-03-01', 13 FROM DUAL UNION ALL
SELECT 4, 100000003, DATE '2016-04-01', 18 FROM DUAL UNION ALL
SELECT 5, 100000004, DATE '2016-05-01', 12 FROM DUAL;

CREATE TABLE payments ( p_id, reference, received_date, p_amount ) AS
SELECT 1, 'PAYMENT01', DATE '2016-01-12', 12 FROM DUAL UNION ALL
SELECT 2, 'PAYMENT02', DATE '2016-01-13', 28 FROM DUAL UNION ALL
SELECT 3, 'PAYMENT03', DATE '2016-02-08',  2 FROM DUAL UNION ALL
SELECT 4, 'PAYMENT04', DATE '2016-02-23', 30 FROM DUAL UNION ALL
SELECT 5, 'PAYMENT05', DATE '2016-05-12', 15 FROM DUAL;
Run Code Online (Sandbox Code Playgroud)

查询:

WITH total_invoices ( i_id, invoice_number, creation_date, i_amount, i_total ) AS (
  SELECT i.*,
         SUM( i_amount ) OVER ( ORDER BY creation_date, i_id )
  FROM   invoices i
),
total_payments ( p_id, reference, received_date, p_amount, p_total ) AS (
  SELECT p.*,
         SUM( p_amount ) OVER ( ORDER BY received_date, p_id )
  FROM   payments p
)
SELECT invoice_number,
       reference,
       LEAST( p_total, i_total )
         - GREATEST( p_total - p_amount, i_total - i_amount ) AS used_pay_amount,
       GREATEST( i_total - p_total, 0 ) AS open_inv_amount
FROM   total_invoices
       INNER JOIN
       total_payments
       ON (    i_total - i_amount < p_total
           AND i_total > p_total - p_amount );
Run Code Online (Sandbox Code Playgroud)

说明:

两个子查询factoring(WITH ... AS ())子句只是向invoicespayments表添加一个额外的虚拟列,其中包含发票/付款金额的累计总和.

您可以将范围与每张发票(或付款)相关联,作为发票(付款)之前的欠款(已付)累计金额以及之后的欠款(已付款)累计金额.然后可以在这些范围重叠的地方连接这两个表.

open_inv_amount是累计发票金额与累计支付金额之间的正差额.

used_pay_amount稍微复杂一些,但你需要找到当前累计发票和付款总额的低,以前的累积发票和付款总额较高的区别.

输出:

INVOICE_NUMBER REFERENCE USED_PAY_AMOUNT OPEN_INV_AMOUNT
-------------- --------- --------------- ---------------
     100000000 PAYMENT01              12              18
     100000000 PAYMENT02              18               0
     100000001 PAYMENT02              10              15
     100000001 PAYMENT03               2              13
     100000001 PAYMENT04              13               0
     100000002 PAYMENT04              13               0
     100000003 PAYMENT04               4              14
     100000003 PAYMENT05              14               0
     100000004 PAYMENT05               1              11
Run Code Online (Sandbox Code Playgroud)

更新:

基于mathguy使用UNION加入数据的方法,我想出了一个不同的解决方案,重新使用了我的一些代码.

WITH combined ( invoice_number, reference, i_amt, i_total, p_amt, p_total, total ) AS (
  SELECT invoice_number,
         NULL,
         i_amount,
         SUM( i_amount ) OVER ( ORDER BY creation_date, i_id ),
         NULL,
         NULL,
         SUM( i_amount ) OVER ( ORDER BY creation_date, i_id )
  FROM   invoices
  UNION ALL
  SELECT NULL,
         reference,
         NULL,
         NULL,
         p_amount,
         SUM( p_amount ) OVER ( ORDER BY received_date, p_id ),
         SUM( p_amount ) OVER ( ORDER BY received_date, p_id )
  FROM   payments
  ORDER BY 7,
           2 NULLS LAST,
           1 NULLS LAST
),
filled ( invoice_number, reference, i_prev, i_total, p_prev, p_total ) AS (
  SELECT FIRST_VALUE( invoice_number )  IGNORE NULLS OVER ( ORDER BY ROWNUM ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING ),
         FIRST_VALUE( reference )       IGNORE NULLS OVER ( ORDER BY ROWNUM ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING ),
         FIRST_VALUE( i_total - i_amt ) IGNORE NULLS OVER ( ORDER BY ROWNUM ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING ),
         FIRST_VALUE( i_total )         IGNORE NULLS OVER ( ORDER BY ROWNUM ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING ),
         FIRST_VALUE( p_total - p_amt ) IGNORE NULLS OVER ( ORDER BY ROWNUM ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING ),
         COALESCE(
           p_total,
           LEAD( p_total ) IGNORE NULLS OVER ( ORDER BY ROWNUM ),
           LAG( p_total )  IGNORE NULLS OVER ( ORDER BY ROWNUM )
         )
   FROM  combined
),
vals ( invoice_number, reference, upa, oia, prev_invoice ) AS (
  SELECT invoice_number,
         reference,
         COALESCE( LEAST( p_total - i_total ) - GREATEST( p_prev, i_prev ), 0 ),
         GREATEST( i_total - p_total, 0 ),
         LAG( invoice_number ) OVER ( ORDER BY ROWNUM )
  FROM   filled 
)
SELECT invoice_number,
       reference,
       upa AS used_pay_amount,
       oia AS open_inv_amount
FROM   vals
WHERE  upa > 0
OR     ( reference IS NULL AND invoice_number <> prev_invoice AND oia > 0 );
Run Code Online (Sandbox Code Playgroud)

说明:

combined子查询保条款加入了两个表UNION ALL,并生成发票和支付的金额累计总量.它做的最后一件事就是按行的递增累计总数对行进行排序(如果存在联系,它将按照创建的顺序将付款放在发票之前).

filled子查询保条款将填补之前生成的表,这样,如果一个值为null,则它会从下一个非空行的值(如果有没有付款的发票这时就会发现总的先前行的先前付款).

vals子查询保条款适用相同的计算,我以前的查询(见上文).它还添加了该prev_invoice列,以帮助识别完全未付的发票.

最终SELECT获取值并过滤掉不必要的行.