2016-05-25 2 views
7

わかりやすい方法で私の挑戦を記述することができれば幸いです。 私はこのように見えるのOracle Database 12cの上に2つのテーブルがあります。だから私は選択を持っているしたいOracle SQLは別の値に達するまで値を集計します

表名「請求書」

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 

表名「支払い」

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 

を(おそらくOracleの分析関数を使用していますが、私はそれに精通していません)、請求書の金額に達するまで支払額が集計され、日付順に並べられます。例えば2回の支払いの合計が請求書の金額よりも多い場合は、最後の支払い金額の残りの部分を次の請求書に使用する必要があります。結果はこのようにする必要があります。この例では

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 

「シンプル」select文と解決策があればそれはいいだろう。お時間を事前に

THX ...

+0

これはPL/Sqlで実践することができますが、正直なところ私はそれが単純なSQLでできているのではないかと疑う余地があります – Gar

+0

あなたの例は間違っています - 支払い4の場合、あなたは13 + 13 + 14を持っています。これは30ではなく40になります。 – Boneist

+1

@Boneist - 私は先に進み、その誤りを訂正しました。私も 'oracle 12'タグを削除しました。この問題は、Oracleのずっと下のバージョンでは解決できます。 – mathguy

答えて

7

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; 

クエリ

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); 

説明

2つのサブクエリファクタリング(WITH ... AS())句は、invoicesテーブルとpaymentsテーブルに追加の仮想カラムを追加し、請求書/支払い額の累計を加算します。

請求書(支払)が置かれる前の累積金額とその後の累積金額(支払済)として、各請求書(または支払)に範囲を関連付けることができます。これらの範囲が重複する場合は、2つのテーブルを結合することができます。

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 

更新

データを結合するためにUNIONを使用するmathguyの方法に基づき、私は再使用して私のコードの一部を別の解決策を考え出しました。

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); 

説明

combinedサブクエリファクタリング句はUNION ALLを持つ2つのテーブルを結合し、請求して支払われた金額の累積合計を生成します。最後の行は、行の昇順累計で行を並べ替えることです(結び付いている場合は、作成された順に請求書の前に支払いを行います)。

filledサブクエリファクタリング句は、以前に生成されたテーブルを埋めるので、値がNULLの場合は、次の非ヌル行から値を取得します(支払いがない請求書がある場合、前の行からの前払いの合計を見つける)。

サブクエリファクタリング句は、前のクエリと同じ計算を適用します(上記参照)。また、完全に未払いの請求書の識別に役立つprev_invoice列が追加されています。

最後のSELECTは値を取り、不要な行を除外します。

+0

残念ながら、残念ながら、残念ながら、残念ながら、残念ながら、残念ながら、PAYMENT02の残りの10個だけを使用するべきであったときは、 '10000000001 \t PAYMENT02 15 'を返します(18は前回の請求書の支払いに使用されたためです)。 – Boneist

+0

@Boneist私もそれに気付きました。それは合理的に簡単な修正でした。 – MT0

+0

「合理的にシンプルな」?!私はあなたの答えを理解するためにまだ苦労しています!私はあなたが本当にツイストのある脳を持っていると思います。(私はそれを褒め言葉として意味します) – Boneist

1

ここでは、参加を必要としないソリューションです。データの量が重要な場合は、これが重要です。私は、Oracle 11.2の無料版(XE)を使用して、ラップトップで何らかのテストを行いました(商用ではありません)。 MT0のソリューションを使用すると、10kの請求書と10万回の支払いがある場合、参加するクエリには約11秒かかります。 50kの請求書と50,000回の支払いの場合、クエリには287秒(約5分)かかりました。 2つの50kテーブルを結合するには25億回の比較が必要なので、これは理解できます。

以下の代替は、共用体を使用します。他のソリューションで結合が行う作業を行うには、lag()last_value()を使用します。このユニオンベースのソリューションは、50k請求書と50,000ペイメントで、私のラップトップで0.5秒未満でした(!)

私は設定を少し簡略化しました。 i_id,invoice_numberおよびcreation_dateはすべて、1つの目的でのみ使用されます。請求書金額を注文するためです。私はテスト目的で

..私はそうのようなテーブルinvoicespayments作成し、その目的のためだけinv_id(送り状番号)を使用して、支払いのための類似した:ソリューションをテストするために、次に

create table invoices (inv_id, inv_amt) as 
    (select level, trunc(dbms_random.value(20, 80)) from dual connect by level <= 50000); 
create table payments (pmt_id, pmt_amt) as 
    (select level, trunc(dbms_random.value(20, 80)) from dual connect by level <= 50000); 

を、私は1枚のまたは複数の請求書への支払いの割り当て、および1回のまたは複数の支払から請求書の支払いを表示するために見て、私の解決策では

create table bal_of_pmts as 
    [select query, including the WITH clause but without the setup CTE's, comes here] 

;:私はこのように、CTASを移入するためにクエリを使用します元の記事で議論されたアウトプットは、この情報の半分だけをカバーしていますが、対称性のために、私は両方の半分を示す方が理にかなっています。左半分はオリジナルのポストを要求するものであるか

INV_ID  PAID  UNPAID  PMT_ID  USED AVAILABLE 
---------- ---------- ---------- ---------- ---------- ---------- 
     1   12   18  101   12   0 
     1   18   0  103   18   10 
     2   10   15  103   10   0 
     2   2   13  105   2   0 
     2   13   0  107   13   17 
     3   13   0  107   13   4 
     4   4   14  107   4   0 
     4   14   0  109   14   1 
     5   1   11  109   1   0 
     5   11   0     11 

お知らせ:(オリジナルのポストと同じ入力のための)出力がinv_idpmt_idの私のバージョンで、次のようになります。最後に余分な行があります。最後の支払いの金額が明らかになった残高を示す、11の支払いのための支払IDのNULLを確認します。id = 6の請求書が22の場合は、もう1つの行が表示されます。つまり、実際には意味を持たない支払いから「請求済み」としてその請求書の全額(22)が表示されます。まだカバーされていない。

クエリは、結合アプローチよりも少し理解しやすいかもしれません。それが何であるかを見るには、中間結果、特にCTE cWITH句の中で)を注意深く見てください。

with invoices (inv_id, inv_amt) as (
     select 1, 30 from dual union all 
     select 2, 25 from dual union all 
     select 3, 13 from dual union all 
     select 4, 18 from dual union all 
     select 5, 12 from dual 
    ), 
    payments (pmt_id, pmt_amt) as (
     select 101, 12 from dual union all 
     select 103, 28 from dual union all 
     select 105, 2 from dual union all 
     select 107, 30 from dual union all 
     select 109, 15 from dual 
    ), 
    c (kind, inv_id, inv_cml, pmt_id, pmt_cml, cml_amt) as (
     select 'i', inv_id, sum(inv_amt) over (order by inv_id), null, null, 
       sum(inv_amt) over (order by inv_id) 
      from invoices 
     union all 
     select 'p', null, null, pmt_id, sum(pmt_amt) over (order by pmt_id), 
       sum(pmt_amt) over (order by pmt_id) 
      from payments 
    ), 
    d (inv_id, paid, unpaid, pmt_id, used, available) as (
     select last_value(inv_id) ignore nulls over (order by cml_amt desc), 
       cml_amt - lead(cml_amt, 1, 0) over (order by cml_amt desc), 
       case kind when 'i' then 0 
         else last_value(inv_cml) ignore nulls 
            over (order by cml_amt desc) - cml_amt end, 
       last_value(pmt_id) ignore nulls over (order by cml_amt desc), 
       cml_amt - lead(cml_amt, 1, 0) over (order by cml_amt desc), 
       case kind when 'p' then 0 
         else last_value(pmt_cml) ignore nulls 
            over (order by cml_amt desc) - cml_amt end 
     from c 
    ) 
select inv_id, paid, unpaid, pmt_id, used, available 
from  d 
where paid != 0 
order by inv_id, pmt_id 
; 

ほとんどの場合、CTE dが必要です。ただし、複数の請求書の累積合計がいくつかの支払いの累積合計と正確に等しい場合は、paid = unpaid = 0の行が追加されます(MT0の結合ソリューションにはこの問題はありません)情報を持たない行がない場合は、paid != 0のフィルタを追加する必要がありました。

+0

出力の最後の行は 'paid'と' used'は0で、 'unpaid'は11 - これは「フィーチャー」として言及していますが、逆の順序はバグのように見えます。もう一つの問題は、支払いの行を 'select 107,30 from dual union all'から' select 107、dual 26 all from dual union all'に変更して(最後の請求書が完全に未払いになるように)、次に請求書5の番号は、私は正しいとは思わない。 – MT0

+0

@ MT0 - 'pmt_amt'が' pmt_id = 107'の '30'から' 26'に変更された場合、最後の2行は私の回答の最後の行のように見えます: 'inv_id = 4'は' paid = 3' 'inv_id = 5'は' paid = 12'を示しています。 '12 'は最後の請求書の全額ですので、その数は正しいです。どちらの問題も同じで、「発見された」金額の扱いです。それらは理想的な方法では表示されません(クエリを調整する必要があります)。しかし、私がそれを解釈する方法では、表が示すのは、単純に 'pmt_id = NULL'で行われた支払いであり、未知の将来の支払いを意味します。 (下に続きます) – mathguy

+0

@ MT0 - (上記から継続)未払いの残高は、部分的に支払われた請求書と何も支払われていない請求書の両方から表示されます。 'pmt_id = NULL'はまだ支払いが行われていないことを意味し、未払いであることを覚えておく必要があります。それを示す適切な方法は未払い未払い金額(支払いが請求書を超えた場合に最後に残された「クレジット」を対称的に含む)の列数をさらに増やすことです。それはできますが、私の解決策のポイントは違っていました - 参加する代わりに組合を使うこと。 – mathguy

関連する問題