Calculate payment date for each invoice - sql

Please consider the following table transaction: a company regularly sends invoices to their customers that are part of the same order. The companies' clients will often pay only once per so many weeks.
(trans_date in format yyyy-mm-dd)
id order_id trans_type trans_date trans_amount
----------------------------------------------------------
1 1 invoice 2017-01-10 100
2 1 invoice 2017-05-23 150
3 1 invoice 2017-05-28 200
4 2 invoice 2017-03-01 700
5 2 payment 2017-06-16 700
6 1 payment 2017-10-12 450
7 3 invoice 2017-06-24 199
The company would like to see on what date each invoice was paid for. For example: invoice (id) 1 (part of order_id=1 group) was sent on 2017-01-10 and paid on 2017-10-12 (id=6). Invoice with id=7 has not been paid at all.
The desired output would be the payment date for each invoice (payment_date):
id order_id trans_type trans_date trans_amount payment_date
--------------------------------------------------------------------------
1 1 invoice 2017-01-10 100 2017-10-12
2 1 invoice 2017-05-23 150 2017-10-12
3 1 invoice 2017-05-28 200 2017-10-12
4 2 invoice 2017-03-01 700 2017-06-16
5 2 payment 2017-06-16 700
6 1 payment 2017-10-12 450
7 3 invoice 2017-06-24 199
For transactions 5, 6 and 7, the payment_date is empty because it is either a payment (id=5 and 6) or an unpaid invoice (id=7).
I don't understand how I should solve this issue. In combination with regular scripting, I would get the whole set and loop through it to find each payment. But how can this be solved in SQL only?
Any help would be greatly appreciated!

Did you try a simple left join?
Below code is standard SQL.
Select a.id , a.order_id, a.trans_type, a.trans_date, a.trans_amount, isnull(b.trans_date, '') As payment_date
From transaction a
Left join transaction b
On a.order_id = b.order_id
And a.trans_type = 'invoice'
And b.trans_type = 'payment'

You can do a cumulative sum of payments and invoices and get the first date when the payment total meets or exceeds the invoice total:
with ip as (
select ip.*,
sum(case when ip.trans_type = 'invoice' then ip.trans_amount else 0 end) over (order by ip.trans_date) as running_invoice,
sum(case when ip.trans_type = 'payment' then ip.trans_amount else 0 end) over (order by ip.trans_date) as running_payment,
from invoicepayments i
)
select ip.*,
(select min(ip2.trans_date)
from ip ip2
where ip2.running_payment >= ip.running_invoice and
ip.trans_type = 'invoice'
) as payment_date
from ip;

Related

Grouping and Summarize SQL

My table looks like the following:
income
date
productid
invoiceid
customerid
300
2015-01-01
A
1234551
1
300
2016-01-02
A
1234552
1
300
2016-01-03
B
1234553
2
300
2016-01-03
A
1234553
2
300
2016-01-04
C
1234554
3
300
2016-01-04
C
1234554
3
300
2016-01-08
A
1234556
3
300
2016-01-08
B
1234556
3
300
2016-01-11
C
1234557
3
I need to know : Number of invoices per customer, how many customers in total (for example one invoice = several customers, two invoices = two customers, three invoices = three customers, and so..).
What is the syntax for this query?
In my sample data above, customer 1 has two invoices, customer 2 one invoice and customer 3 three invoices. So there is one customer each with a count of 1, 2, and 3 invoices in my example.
Expected result:
invoice_count
customers_with_this_invoice_count
1
1
2
1
3
1
I tried this syntax and I'm still stuck:
select * from
(
select CustomerID,count(distinct InvoiceID) as 'Total Invoices'
from exam
GROUP BY CustomerID
) a
Select Count(customerID),CustomerID From a
Group By customerID
Having Count(customerID) > 1

Need to group records based on matching reversal in sql

I have a tricky scenario to aggregate the data.
Data in my source table is as follows.
CustomerId Transaction Type Transaction Amount
1 Payment 100
1 ReversePayment -100
1 payment 100
1 ReversePayment -100
1 Payment 100
1 Payment 100
Requirement is as follows:
If the payment as a assoociated Reversepayment with matched amount, sum these two records.
If the payment does not have an associated Reverse payment, consider it as orphan(dont sum it).
I want output to be like this.
CustomerId Transaction Type Transaction Amount
1 Payment,ReversePayment 0
1 payment,ReversePayment 0
1 payment 100
1 Payment 100
In this scenario,
First record which is payment has an associated reverse payment (2nd record), Hence the sum becomes 0
Third record which is payment has an associated reverse payment (4th record), then the sum becomes 0
Fifth and sixth does not have associated reversals. dont sum these records.
Second Example:
Data in the source as follows:
CustomerId Transaction Type Transaction Amount
1 Payment 100
1 ReversePayment -100
1 payment 300
1 ReversePayment -300
1 Payment 400
1 Payment 500
Expected Output
CustomerId Transaction Type Transaction Amount
1 Payment,ReversePayment 0
1 payment,ReversePayment 0
1 payment 400
1 Payment 500
Second example requirement:
-As first and second records (payment and its associated reverse payment got
matched) ,sum these two records, output is 0.
- As third and fourth records (payment and its associated reverse payment got
matched), sum these two records, output is 0.
- Fifth and sixth does not have associated reversals. don't sum these records.
I got solutions in group, but data is not always guaranteed to have orphan records as 'payments'. Some times they are 'Payments' and some times they are 'ReversePayments'. Can some help me get ouptut like the below (using rank or rownumber functions ) so that i can group by using RRR column.
CustomerId Transaction Type Transaction Amount RRR
1 Payment 100 1
1 ReversePayment -100 1
1 payment 100 2
1 ReversePayment -100 2
1 Payment 100 3
1 Payment 100 4
CustomerId Transaction Type Transaction Amount RRR
1 Payment 100 1
1 ReversePayment -100 1
1 payment 300 2
1 ReversePayment -300 2
1 Payment 400 3
1 Payment 500 4
You can enumerate the different types and then aggregate:
select customerid,
listagg(ttype, ',') within group (order by ttype) as types,
sum(amount) as amount
from (select t.*,
row_number() over (partition by customerid, ttype, amount order by customerid) as seqnum
from t
) t
group by customerid, seqnum;
Edited to include your second scenario:
Using rownum to enforce inherent ordering (i.e. transactions happened in the order you've listed ), since your example is missing a transaction id or transaction time
SQL> select * from trans_data2;
CUSTOMER_ID TRANSACTION_TY TRANSACTION_AMOUNT
----------- -------------- ------------------
1 Payment 100
1 ReversePayment -100
1 payment 300
1 ReversePayment -300
1 Payment 400
1 Payment 500
6 rows selected.
SQL> select customer_id,
2 case
3 when upper(next_transaction) = 'REVERSEPAYMENT' then transaction_type||','||next_transaction
4 else transaction_type
5 end transaction_type,
6 case
7 when upper(next_transaction) = 'REVERSEPAYMENT' then transaction_amount + next_transaction_amount
8 else transaction_amount
9 end transaction_amount
10 from (
11 select customer_id, transaction_type, transaction_amount,
12 lead (transaction_type) over ( partition by customer_id order by transaction_id ) next_transaction,
13 nvl(lead (transaction_amount) over ( partition by customer_id order by transaction_id),0) next_transaction_amount
14 from ( select rownum transaction_id, t.* from trans_data2 t )
15 ) where upper(transaction_type) = 'PAYMENT'
16 ;
CUSTOMER_ID TRANSACTION_TYPE TRANSACTION_AMOUNT
----------- ----------------------------- ------------------
1 Payment,ReversePayment 0
1 payment,ReversePayment 0
1 Payment 400
1 Payment 500

Finding financial patterns of rows - sql with recursion

Let's consider I have a following table:
id date transaction_type amount
1 2017-01-01 deposit 30
1 2017-01-01 deposit 20
1 2017-01-02 withdrawal -20
1 2017-01-02 deposit 40
1 2017-01-04 deposit 50
1 2017-01-05 withdrawal -100
1 2017-01-07 withdrawal -10
1 2017-01-09 deposit 100
1 2017-01-11 withdrawal -50
1 2017-01-21 deposit 20
1 2017-01-22 deposit 30
1 2017-01-31 withdrawal -60
2 2017-01-01 deposit 200
... ... ... ...
The dates in the table are ordered from the oldest to the newest for each id (timestamp is not visible). I would like to find how many times a specific transaction pattern took place:
deposit -> deposit -> withdrawal, and the time between the first deposit and the withdrawal is 7 days or less.
So for the customer with id = 1, I would have 2 such cases (the third one does not satisfy the time condition).
As a result, I would like to get the following table:
id number_of_times
1 2
2 ...
... ...
Is it something that can be done in SQL? Would I require recursion to get to the final table?
UPDATE:
As correctly pointed out, there are no intervening transactions - but what if there were some? Like any number of other transactions between 1st and 2nd deposit etc.
Assuming you have no intervening transactions:
select id, count(*)
from (select t.*,
lead(transaction_type) over (partition by id order by date) as next_tt,
lead(transaction_type, 2) over (partition by id order by date) as next_tt_2,
lead(date, 2) over (partition by id order by date) as next_date_2
from t
) t
where transaction_type = 'deposit' and next_tt = 'deposit' and
next_tt_2 = 'withdrawal' and
next_date_2 < date + interval '7 day'
group by id;

SQL Group by range and total column

With the following function and stored procedure i get a resultset with 2 columns.
What i need additional is a third column total for all open invoices inner score range
It would great for any idea.
ALTER FUNCTION dbo.OpenOrders
(
#MandantId int
)
RETURNS TABLE
AS
RETURN
SELECT SUM(Invoices.Amount - ISNULL(Payment.Amount, 0)) AS Op
FROM Invoices INNER JOIN
Orders ON Invoices.OrderId = Orders.OrderId LEFT OUTER JOIN
Payment ON Invoices.InvoiceId = Payment.InvoiceId
WHERE (Orders.MandantId = #MandantId)
GROUP BY Invoice.InvoiceId, Invoices.Amount
HAVING (SUM(Invoices.Amount - ISNULL(Payment.Amount, 0)) <> 0)
ALTER PROCEDURE dbo.GetOpRanges
#MandantId int
AS
BEGIN
SELECT * INTO #tmp_ranges
FROM
//wrong in first post -> OPDebitorByMandant(#MandantId)
OpenOrders(#MandantId)
SELECT op AS [score range], COUNT(*) AS [number of occurences]
FROM
(SELECT CASE
WHEN op BETWEEN 0 AND 50 THEN ' 0- 50'
WHEN op BETWEEN 50 AND 100 THEN ' 50-100'
WHEN op BETWEEN 100 AND 500 THEN '100-500'
ELSE '500 and >' END AS op
FROM [#tmp_ranges]) AS t
GROUP BY op
RETURN
Result:
score range number of occurences range
------------------+-------------
0- 50 23
50-100 4
100-500 4
500 and > 21
What i need additional is a third column total for all open invoices inner score range.
Target result:
score range number of occurences Total
-----------+--------------------+------
0- 50 23 1.150
50-100 4 400
100-500 4 2.000
500 and > 21 22.000
Tables:
Invoices
InvoiceId CustomerId OrderId DateOfInvoice Amount
----------+----------+-------+-------------+------
1 1 20 20160301 1000.00
2 2 22 20160501 2000.00
3 1 102 20160601 3000.00
...
Orders
OrderId MandantId CustomerId DateOfOrder Amount
-------+---------+----------+-----------+-----------
20 1 1 20160101 1000.00
22 1 2 20160101 2000.00
102 1 1 20160101 3000.00
...
Payment
PaymentId MandantId CustomerId InvoiceId OrderId DateOfPayment Amount
---------+---------+----------+---------+-------+-------------+-------------
1 1 1 1 20 20160310 1000.00
2 1 2 2 22 20160505 2000.00
3 1 1 3 102 20160610 3000.00
...
hope it's helpfull and thanks again in advance for any solution

Accounting Overdue Query

I need a query to get open invoices and overdue (if any).
I have a table named "Invoices" with this basic structure:
InvoiceNo CustID InvoiceDate Amount DueDate Credit
1 1 01/04/2015 120.00 01/31/2015
2 2 01/13/2015 72.00 01/20/2015
3 1 01/12/2015 63.00 TRUE
4 2 02/18/2015 43.00 02/10/2015
5 2 02/24/2015 16.00 TRUE
A table named "Payments" reflecting the customers payments:
PaymentNo CustID PaymentDate Amount Refund
I have also an "Opening Balance" value for each customer
I need to be able to see on the query results of customers having a balance <> 0. Something like that:
CustID Balance Amount Overdue Days Overdue
I use this query to show open invoices, it works fine but I'm stuck with the overdue deal.
Select Customers.CustID
, Customers.DisplayName As 'Name'
, COALESCE(Customers.OpeningBalance,0)
+(( Select COALESCE(Sum(Invoices.Amount),0) From Invoices Where Credit = 0 And Invoices.CustID = Customers.CustID )
+( Select COALESCE(Sum(Payments.Amount),0) From Payments Where Refund = 1 And Payments.CustID = Customers.CustID ) )
-(( Select COALESCE(Sum(Invoices.Amount),0) From Invoices Where Credit = 1 And Invoices.CustID = Customers.CustID )
+( Select COALESCE(Sum(Payments.Amount),0) From Payments Where Refund = 0 And Payments.CustID = Customers.CustID )) as Balance
From Customers
Where Exists ( Select * From Invoices
Where Invoices.CustID = Customers.CustID )
#Zajonc
The overdue balance is calculated according to the invoice amount and the payment amount relatively to their dates.
Scenario:
assuming a customer has an invoice #1 with a due date dated 01/15/2015 and with the amount of 140.00
assuming that that customer made a payment of 40.00 on the 01/20/2015.
The days overdue would be the days between today and the due date.
The due date balance would be 140.00 - 40.00 = 100.00.
That's for only one invoice and one payment transaction but since the reality is a bit more complicated the calculation should reflects the accumulation of both of the days and balance overdue.ie:
Assuming we execute the query on 02/15/2015 with the following data on our customer:
Invoice # Amount Due Date
1 140.00 01/15/2015
2 360.00 02/08/2015
Payment # Amount Payment Date
1 40.00 01/20/2015
2 100.00 02/18/2015
The days overdue would be:
( days elapsed between 02/15/2015 and 01/15/2015 ) +
( days elapsed between 02/15/2015 and 02/08/2015 ) = 31 + 7 = 38 days.
The balance overdue would be 100.00 (the customer made a payment 3 days after the query's date ) + 360.00 = 460.00
I hope it clarifies.
Thanks.