SQL Query to fetch the latest payment method and invoice date - sql

There are four tables
First - Customer
Second - Invoice
Third - Supplier
Fourth - Supplier_Remit
Tables details are mentioned below
Customer_id
Customer_Account_number
Customer_Status
Supplier_id
Supplier_Remit_id
1
1501
Active
11
111
2
1502
Inactive
12
112
3
1503
Active
13
113
4
1504
Active
14
114
5
1505
Inactive
15
115
Invoice_Date
Invoice_Amount
Invoice_Number
Payment Method
Customer_id
01/01/2023
100
1000001
Cash
1
12/01/2022
150
1000002
Credit Card
1
11/09/2022
200
1000003
Credit Card
1
12/09/2022
300
1000004
Cash
2
04/15/2022
1000
1000005
Cash
2
04/15/2022
1000
1000006
Credit Card
3
10/31/2022
250
1000007
Cash
4
10/25/2022
250
1000008
Cash
4
09/20/2022
130
1000009
Credit Card
5
05/20/2022
120
10000010
Credit Card
5
Supplier_Name
Supplier_id
ABC
11
ACCC
12
ADEF
13
AJKL
14
AFLR
15
City
Country
Supplier_Remit_id
Supplier_id
Boston
US
111
11
Oak
US
112
12
Albany
US
113
13
Madison
US
114
14
Los Ang
US
115
15
I need help in finding the most recent payment method, most recent invoice amount, no of count of invoices missing for current year (2023) and no of count of invoices missing for previous year(2022)
I have written query to find first few columns but unable to write further to get the above mentioned details
select c.customer_id,c.customer_account_number,c.customer_status,sr.country,max(i.invoice_date) as Latest receieved_Invoice_date
from
customer c,
invoice i,
supplier s,
supplier_Remit sr
where
c.customer_status='Active' and
sr.supplier_id=s.supplier_id and
c.supplier_remit_id=sr.supplier_remit_id and
c.customer_id=i.customer_id
group by
c.customer_id,c.customer_account_number,c.customer_status,sr.country;
My expected output would be as below
Customer_id
Cust_Acct_Num
Cust_Status
Country
Last_Inv_Rec_Date
1
1501
Active
US
01/01/2023
3
1503
Active
US
04/15/2022
4
1504
Active
US
10/31/2022
Latest_Paym_Method
Lastest_Inv_Amt
Count of Missing Inv for Curr Yr
Cash
100
0
Credit card
1000
1
Cash
250
1
Count of Missing Invoices for Prev Year
10
11
11

You can use MAX(...) KEEP (DENSE_RANK LAST ORDER BY invoice_date) to get values for the latest invoice and conditional aggregation to count the number of months where there are invoices and then subtract from the total number of months to find the missing invoices:
SELECT c.Customer_id,
c.Customer_Account_number,
c.Customer_Status,
r.country,
i.last_invoice_date,
i.latest_payment_method,
i.latest_invoice_amount,
EXTRACT(MONTH FROM SYSDATE) - COALESCE(i.missing_invoices_this_year, 0)
AS missing_invoices_this_year,
12 - COALESCE(i.missing_invoices_last_year, 0)
AS missing_invoices_last_year
FROM customer c
INNER JOIN supplier_remit r
ON (c.supplier_id = r.supplier_id)
LEFT OUTER JOIN (
SELECT customer_id,
MAX(invoice_date) AS last_invoice_date,
MAX(payment_method) KEEP (DENSE_RANK LAST ORDER BY invoice_date)
AS latest_payment_method,
MAX(invoice_amount) KEEP (DENSE_RANK LAST ORDER BY invoice_date)
AS latest_invoice_amount,
COUNT(
DISTINCT
CASE
WHEN invoice_date < SYSDATE
AND invoice_date >= TRUNC(SYSDATE, 'YY')
THEN TRUNC(invoice_date, 'MM')
END
) AS missing_invoices_this_year,
COUNT(
DISTINCT
CASE
WHEN invoice_date < TRUNC(SYSDATE, 'YY')
AND invoice_date >= ADD_MONTHS(TRUNC(SYSDATE, 'YY'), -12)
THEN TRUNC(invoice_date, 'MM')
END
) AS missing_invoices_last_year
FROM invoice
GROUP BY customer_id
) i
ON (c.customer_id = i.customer_id)
WHERE c.customer_status = 'Active';
Which, for the sample data:
CREATE TABLE customer (Customer_id, Customer_Account_number, Customer_Status, Supplier_id, Supplier_Remit_id) AS
SELECT 1, 1501, 'Active', 11, 111 FROM DUAL UNION ALL
SELECT 2, 1502, 'Inactive', 12, 112 FROM DUAL UNION ALL
SELECT 3, 1503, 'Active', 13, 113 FROM DUAL UNION ALL
SELECT 4, 1504, 'Active', 14, 114 FROM DUAL UNION ALL
SELECT 5, 1505, 'Inactive', 15, 115 FROM DUAL;
CREATE TABLE invoice (Invoice_Date, Invoice_Amount, Invoice_Number, Payment_Method, Customer_id) AS
SELECT DATE '2023-01-01', 100, 1000001, 'Cash', 1 FROM DUAL UNION ALL
SELECT DATE '2022-12-01', 150, 1000002, 'Credit Card', 1 FROM DUAL UNION ALL
SELECT DATE '2022-11-09', 200, 1000003, 'Credit Card', 1 FROM DUAL UNION ALL
SELECT DATE '2022-12-09', 300, 1000004, 'Cash', 2 FROM DUAL UNION ALL
SELECT DATE '2022-04-15', 1000, 1000005, 'Cash', 2 FROM DUAL UNION ALL
SELECT DATE '2022-04-15', 1000, 1000006, 'Credit Card', 3 FROM DUAL UNION ALL
SELECT DATE '2022-10-31', 250, 1000007, 'Cash', 4 FROM DUAL UNION ALL
SELECT DATE '2022-10-25', 250, 1000008, 'Cash', 4 FROM DUAL UNION ALL
SELECT DATE '2022-09-20', 130, 1000009, 'Credit Card', 5 FROM DUAL UNION ALL
SELECT DATE '2022-05-20', 120, 10000010, 'Credit Card', 5 FROM DUAL;
CREATE TABLE supplier (Supplier_Name, Supplier_id) AS
SELECT 'ABC', 11 FROM DUAL UNION ALL
SELECT 'ACCC', 12 FROM DUAL UNION ALL
SELECT 'ADEF', 13 FROM DUAL UNION ALL
SELECT 'AJKL', 14 FROM DUAL UNION ALL
SELECT 'AFLR', 15 FROM DUAL;
CREATE TABLE supplier_remit (City, Country, Supplier_Remit_id, Supplier_id) AS
SELECT 'Boston', 'US', 111, 11 FROM DUAL UNION ALL
SELECT 'Oak', 'US', 112, 12 FROM DUAL UNION ALL
SELECT 'Albany', 'US', 113, 13 FROM DUAL UNION ALL
SELECT 'Madison', 'US', 114, 14 FROM DUAL UNION ALL
SELECT 'Los Ang', 'US', 115, 15 FROM DUAL;
Outputs:
CUSTOMER_ID
CUSTOMER_ACCOUNT_NUMBER
CUSTOMER_STATUS
COUNTRY
LAST_INVOICE_DATE
LATEST_PAYMENT_METHOD
LATEST_INVOICE_AMOUNT
MISSING_INVOICES_THIS_YEAR
MISSING_INVOICES_LAST_YEAR
1
1501
Active
US
2023-01-01 00:00:00
Cash
100
0
10
3
1503
Active
US
2022-04-15 00:00:00
Credit Card
1000
1
11
4
1504
Active
US
2022-10-31 00:00:00
Cash
250
1
11
fiddle

In order to find what's missing, you have to first define what should be there, so you need to create a calendar of every month. Then you can use outer joins to the invoice table to find where there aren't any records for that month for that customer. There are lots of ways to write SQL to do this. Here's one:
WITH months AS(SELECT /*+ MATERIALIZE */ *
FROM (SELECT 'Current' year,
ADD_MONTHS(TRUNC(SYSDATE,'YYYY'),ROWNUM-1) month_start
FROM [any table with at least 12 rows]
WHERE ROWNUM <= 12)
WHERE month_start < SYSDATE
UNION ALL
SELECT 'Previous' year,
ADD_MONTHS(TRUNC(ADD_MONTHS(SYSDATE,-12),'YYYY'),ROWNUM-1)
FROM [any table with at least 12 rows]
WHERE ROWNUM <= 12)
SELECT customer.*,
inv.invoice_amount most_recent_invoice_amount,
inv.payment_method most_recent_payment_method,
(SELECT COUNT(*)
FROM months,
invoice
WHERE months.year = 'Current'
AND months.month_start = TRUNC(invoice_date(+),'MM')
AND invoice.customer_id(+) = customer.customer_id
AND invoice.customer_id IS NULL) missed_current_year_months,
(SELECT COUNT(*)
FROM months,
invoice
WHERE months.year = 'Previous'
AND months.month_start = TRUNC(invoice_date(+),'MM')
AND invoice.customer_id(+) = customer.customer_id
AND invoice.customer_id IS NULL) missed_previous_year_months
FROM customer
OUTER APPLY (SELECT invoice_amount,
payment_method
FROM (SELECT invoice_amount,
payment_method,
ROW_NUMBER() OVER (ORDER BY invoice_date DESC) seq
FROM invoice
WHERE invoice.customer_id = customer.customer_id)
WHERE seq = 1) inv

Related

CONSECUTIVE DAYS QUERY [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 11 months ago.
Improve this question
I have an Oracle DB Connection that has data (SELECT * FROM SALES) as in the picture, i want a query that gives me which 3 consecutive days are those who have the sum of PREMIUM_TOTAL > 100.
I have tried with the method lead, lag , DATADIFF but failed. Also i'm new at this, if you can give me hints please.
If you want 3 rows from successive days then you can use a recursive query:
WITH successive_days (day, products, total, depth) AS (
SELECT entry_date,
TO_CHAR(product_id),
premium_total,
1
FROM table_name
UNION ALL
SELECT s.day + 1,
s.products || ',' || t.product_id,
s.total + t.premium_total,
s.depth + 1
FROM successive_days s
INNER JOIN table_name t
ON (s.day + 1 = t.entry_date)
WHERE s.depth < 3
)
SELECT day AS final_day, products, total
FROM successive_days
WHERE depth = 3
AND total >= 100;
Which, for the sample data:
CREATE TABLE table_name (product_id, entry_date, premium_total) AS
SELECT 1, DATE '2022-03-01', 1 FROM DUAL UNION ALL
SELECT 2, DATE '2022-03-01', 20 FROM DUAL UNION ALL
SELECT 4, DATE '2022-03-02', 30 FROM DUAL UNION ALL
SELECT 5, DATE '2022-03-03', 30 FROM DUAL UNION ALL
SELECT 10, DATE '2022-03-21', 12 FROM DUAL UNION ALL
SELECT 11, DATE '2022-03-31', 40.5 FROM DUAL UNION ALL
SELECT 13, DATE '2022-03-05', 70 FROM DUAL UNION ALL
SELECT 12, DATE '2022-03-05', 80 FROM DUAL UNION ALL
SELECT 14, DATE '2022-03-05', 10 FROM DUAL UNION ALL
SELECT 20, DATE '2022-03-06', 20 FROM DUAL UNION ALL
SELECT 21, DATE '2022-03-07', 30 FROM DUAL UNION ALL
SELECT 22, DATE '2022-03-07', 40 FROM DUAL UNION ALL
SELECT 30, DATE '2022-03-08', 20 FROM DUAL UNION ALL
SELECT 31, DATE '2022-03-09', 50 FROM DUAL UNION ALL
SELECT 40, DATE '2022-03-10', 2 FROM DUAL;
Outputs:
FINAL_DAY
PRODUCTS
TOTAL
2022-03-07 00:00:00
13,20,21
120
2022-03-07 00:00:00
13,20,22
130
2022-03-07 00:00:00
12,20,21
130
2022-03-07 00:00:00
12,20,22
140
2022-03-09 00:00:00
21,30,31
100
2022-03-09 00:00:00
22,30,31
110
If you want all the rows (at least 3) that are all within 3 successive days then you can use MATCH_RECOGNIZE:
SELECT MIN(entry_date) AS start_day,
MAX(entry_date) AS final_day,
LISTAGG(product_id, ',') WITHIN GROUP (ORDER BY entry_date) AS products,
SUM(premium_total) AS total
FROM table_name
MATCH_RECOGNIZE(
ORDER BY entry_date
MEASURES
MATCH_NUMBER() AS mno
ALL ROWS PER MATCH
AFTER MATCH SKIP TO NEXT ROW
PATTERN (first_day+ second_day+ third_day* final_day)
DEFINE
first_day AS FIRST(entry_date) = entry_date,
second_day AS FIRST(entry_date) + 1 = entry_date,
third_day AS FIRST(entry_date) + 2 = entry_date,
final_day AS FIRST(entry_date) + 2 = entry_date
AND SUM(premium_total) >= 100
)
GROUP BY mno;
Which, for the sample data, outputs:
START_DAY
FINAL_DAY
PRODUCTS
TOTAL
2022-03-05 00:00:00
2022-03-07 00:00:00
12,13,14,20,21,22
250
2022-03-05 00:00:00
2022-03-07 00:00:00
13,14,20,21,22
170
2022-03-05 00:00:00
2022-03-07 00:00:00
13,20,21,22
160
2022-03-06 00:00:00
2022-03-08 00:00:00
20,21,22,30
110
2022-03-07 00:00:00
2022-03-09 00:00:00
21,22,30,31
140
2022-03-07 00:00:00
2022-03-09 00:00:00
22,30,31
110
db<>fiddle here

I want to get the count of roll number for each month

ID
STUDENT_ID
STATUS_DATE
1002
434120010026
25-FEB-22
1000
434120010026
03-MAY-03
1001
434120010026
25-FEB-22
1020
434120020023
18-MAR-22
1021
434120020025
18-MAR-22
1022
434120020025
16-MAR-22
Tried this
select count(*),
trunc(status_date, 'mm')
from test_studentattendance
group by trunc(status_date, 'mm');
got count of roll number in each month not the roll numbers.
COUNT(*)
TRUNC(STATUS_DATE,'MM')
1
01-MAY-03
2
01-FEB-22
3
01-MAR-22
COUNT(*) is counting the number of rows in each group and is counting students that appear in the same month multiple times.
To get the number on roll each month, you need to COUNT the DISTINCT identifier for each student (which, I assume would be student_id):
SELECT COUNT(DISTINCT student_id) AS number_of_students,
TRUNC(status_date, 'mm') AS month
FROM test_studentattendance
GROUP BY TRUNC(status_date, 'mm');
Which, for your sample data:
CREATE TABLE test_studentattendance (ID, STUDENT_ID, STATUS_DATE) AS
SELECT 1002, 434120010026, DATE '2022-02-25' FROM DUAL UNION ALL
SELECT 1000, 434120010026, DATE '2003-05-03' FROM DUAL UNION ALL
SELECT 1001, 434120010026, DATE '2022-02-25' FROM DUAL UNION ALL
SELECT 1020, 434120020023, DATE '2022-03-18' FROM DUAL UNION ALL
SELECT 1021, 434120020025, DATE '2022-03-18' FROM DUAL UNION ALL
SELECT 1022, 434120020025, DATE '2022-03-16' FROM DUAL;
Outputs:
NUMBER_OF_STUDENTS
MONTH
1
2022-02-01 00:00:00
1
2003-05-01 00:00:00
2
2022-03-01 00:00:00
db<>fiddle here

Find customer ids who ordered more in 2019 than they did in 2018

This one was asked in an interview.
Below is the structure of the table.
Shipments- Shipment_id,Price, Order_id, Ship_date, Delivery_Location, Price, Ship_method , ShipETA,
Customer-Customer_id, order_id, customer_address, prime_eligible
Order - Order_id , Order_Qty, Order_date , Order_location, Item_id , Shipment_id
Item- Item _id , Item_description, Item_Location
Question: List of customer ids who ordered more in 2019 than they did in 2018.
SELECT customer_id
FROM Customer join Order using (order_id)
WHERE YEAR(Order_date) IN (2019)
GROUP BY customer_id
HAVING
SUM(CASE WHEN YEAR(Order_date) = 2019 THEN Order_Qty ELSE 0 END)
> SUM(CASE WHEN YEAR(Order_date) = 2018 THEN Order_Qty ELSE 0 END)
Unfortunately, I don't have sample data can anyone help with the approach to solve this one.
Data model you posted looks somewhat "strange"; I wouldn't keep ORDER_ID in CUSTOMER table, it just doesn't belong there. I'd add CUSTOMER_ID into SHIPMENT instead.
Anyway, here's one option:
sample data in lines #1 - 21
temp CTE calculates summaries (ordered quantities) per customers and years (just or 2018 and 2019)
final query just checks who ordered more items in 2019 than in 2018
SQL> with
2 customer (customer_id, order_id) as
3 (select 'A', 1 from dual union all
4 select 'A', 3 from dual union all
5 select 'B', 2 from dual union all
6 select 'B', 4 from dual union all
7 select 'B', 5 from dual union all
8 --
9 select 'A', 6 from dual
10 ),
11 orders (order_id, order_qty, order_date) as
12 -- A's summaries: 2018: 100 / 2019: 400
13 -- B's summaries: 2018: 400 / 2019: 300 --> should be returned
14 (select 1, 100, date '2018-05-03' from dual union all -- A
15 select 2, 200, date '2018-07-23' from dual union all -- B
16 select 3, 400, date '2019-04-02' from dual union all -- A
17 select 4, 300, date '2019-08-14' from dual union all -- B
18 select 5, 200, date '2018-11-14' from dual union all -- B
19 --
20 select 6, 900, date '2020-01-01' from dual -- A
21 ),
22 -- summaires per customers and years
23 temp as
24 (select c.customer_id,
25 extract(year from o.order_date) as year,
26 sum(o.order_qty) sum_qty
27 from customer c join orders o on o.order_id = c.order_id
28 where extract(year from o.order_date) in (2018, 2019)
29 group by c.customer_id,
30 extract(year from o.order_date)
31 )
32 select t.customer_id
33 from temp t
34 group by t.customer_id
35 having sum(case when t.year = 2019 then t.sum_qty end) <
36 sum(case when t.year = 2018 then t.sum_qty end);
CUSTOMER_ID
-----------
B
SQL>

SQL - Select rows partially depending on another table

I am very new to the database and queries.
I am not sure if this is very basic. Please help me to find a solution.
I have two tables - subscription and customer with primary keys as subscription_id and customer_id respectively.
the customer_id is foreign key pointing to customer table customer_id column.
Below is a sample of table and its data:
subscription
subscription_id customer_id subscription_start_date subscription_end_date subscription_status
1001 1 1-JAN-2020 31-DEC-2020 PENDING
1002 2 1-JUN-2020 31-MAY-2021 PENDING
1003 3 4-JUL-2020 3-JUL-2021 ARCHIVED
1004 4 2-APR-2020 1-APR-2021 PENDING
1005 5 3-APR-2020 2-APR-2021 ARCHIVED
1006 6 21-JAN-2020 20-JAN-2021 PENDING
1007 7 22-JAN-2020 21-JAN-2021 PENDING
Customer
customer_id membership_type membership_start_date membership_status
1 GOLD 1-JAN-2020 ACTIVE
2 PLATINUM 1-JUN-2020 ACTIVE
3 PLATINUM 5-JUL-2020 PROCESSING
4 GOLD 2-APR-2020 PROCESSING
5 GOLD 3-APR-2020 ACTIVE
6 GOLD 21-JAN-2020 PROCESSING
7 GOLD 22-JAN-2019 EXPIRED
I want to query all the subscriptions which satisfies following two criteria
subscription_satus is pending and membership_type is GOLD or PLATINUM and membership_status is ACTIVE.(1001,1002)
subscription_status is archived only when customer membership_type is PLATINUM and the membership_start_date is between subscription_start_date and subscription_end_date and membership_status is PROCESING(1003)
So we should get 1001, 1002 records under criteria 1. and 1003 under criteria 2
Query should look like this; lines #1 - 21 represent sample data, you don't type that. Query you do need begins at line #22 and contains two parts of the WHERE clause: the first satisfies the first condition, while the second satisfies the second condition.
Note that sample MEMBERSHIP_START_DATE for curstomer 3 is 02-JUL-2020 which is not between subscription start (4-JUL-2020) and end (3-JUL-2021) dates, so - 1003 isn't part of the result set. Modify membership start date to e.g. 22-JUL-2020 and it will be.
SQL> with
2 subscription (subscription_id, customer_id, subscription_start_date,
3 subscription_end_date, subscription_status) as
4 (select 1001, 1, date '2020-01-01', date '2020-12-31', 'PENDING' from dual union all
5 select 1002, 2, date '2020-06-01', date '2021-05-31', 'PENDING' from dual union all
6 select 1003, 3, date '2020-07-04', date '2021-07-03', 'ARCHIVED' from dual union all
7 select 1004, 4, date '2020-04-02', date '2021-04-01', 'PENDING' from dual union all
8 select 1005, 5, date '2020-04-03', date '2021-04-02', 'ARCHIVED' from dual union all
9 select 1006, 6, date '2020-01-21', date '2021-01-20', 'PENDING' from dual union all
10 select 1007, 7, date '2020-01-22', date '2021-01-21', 'PENDING' from dual
11 ),
12 customer (customer_id, membership_type, membership_start_date,
13 membership_status) as
14 (select 1, 'GOLD' , date '2020-01-01', 'ACTIVE' from dual union all
15 select 2, 'PLATINUM', date '2020-06-01', 'ACTIVE' from dual union all
16 select 3, 'PLATINUM', date '2020-07-02', 'PROCESSING' from dual union all
17 select 4, 'GOLD' , date '2020-04-02', 'PROCESSING' from dual union all
18 select 5, 'GOLD' , date '2020-04-03', 'ACTIVE' from dual union all
19 select 6, 'GOLD' , date '2020-01-21', 'PROCESSING' from dual union all
20 select 7, 'GOLD' , date '2019-01-22', 'EXPIRED' from dual
21 )
22 select s.subscription_id
23 from subscription s join customer c on c.customer_id = s.customer_id
24 where ( s.subscription_status = 'PENDING'
25 and c.membership_type in ('GOLD', 'PLATINUM')
26 and c.membership_status = 'ACTIVE'
27 )
28 or
29 ( s.subscription_status = 'ARCHIVED'
30 and c.membership_type = 'PLATINUM'
31 and c.membership_start_date between s.subscription_start_date
32 and s.subscription_end_date
33 and c.membership_status = 'PROCESSING'
34 )
35 /
SUBSCRIPTION_ID
---------------
1001
1002
SQL>

SQL select lapsed customers with 30 day frequency by day

The goal is to select the count of distinct customer_id's who have not made a purchase in the rolling 30 day period prior to every day in the calendar year 2016. I have created a calendar table in my database to join to.
Here is an example table for reference, let's say you have customers orders normalized as follows:
+-------------+------------+----------+
| customer_id | date | order_id |
+-------------+------------+----------+
| 123 | 01/25/2016 | 1000 |
+-------------+------------+----------+
| 123 | 04/27/2016 | 1025 |
+-------------+------------+----------+
| 444 | 02/02/2016 | 1010 |
+-------------+------------+----------+
| 521 | 01/23/2016 | 998 |
+-------------+------------+----------+
| 521 | 01/24/2016 | 999 |
+-------------+------------+----------+
The goal output is effectively a calendar with 1 row for every single day of 2016 with a count on each day of how many customers "lapsed" on that day, meaning their last purchase was 30 days or more prior from that day of the year. The final output will look like this:
+------------+--------------+
| date | lapsed_count |
+------------+--------------+
| 01/01/2016 | 0 |
+------------+--------------+
| 01/02/2016 | 0 |
+------------+--------------+
| ... | ... |
+------------+--------------+
| 03/01/2016 | 12 |
+------------+--------------+
| 03/02/2016 | 9 |
+------------+--------------+
| 03/03/2016 | 7 |
+------------+--------------+
This data does not exist in 2015, therefore it's not possible for Jan-01-2016 to have a count of lapsed customers because that is the first possible day to ever make a purchase.
So for customer_id #123, they purchased on 01/25/2016 and 04/27/2016. They should have 2 lapse counts because their purchases are more than 30 days apart. One lapse occurring on 2/24/2016 and another lapse on 05/27/2016.
Customer_id#444 only purchased once, so they should have one lapse count for 30 days after 02/02/2016 on 03/02/2016.
Customer_id#521 is tricky, since they purchased with a frequency of 1 day we will not count the first purchase on 03/02/2016, so there is only one lapse starting from their last purchase of 03/03/2016. The count for the lapse will occur on 04/02/2016 (+30 days).
If you have a table of dates, here is one expensive method:
select date,
sum(case when prev_date < date - 30 then 1 else 0 end) as lapsed
from (select c.date, o.customer_id, max(o.date) as prev_date
from calendar c cross join
(select distinct customer_id from orders) c left join
orders o
on o.date <= c.date and o.customer_id = c.customer_id
group by c.date, o.customer_id
) oc
group by date;
For each date/customer pair, it determines the latest purchase the customer made before the date. It then uses this information to count the lapsed.
To be honest, this will probably work well on a handful of dates, but not for a full year's worth.
Apologies, I didn't read your question properly the first time around. This query will give you all the lapses you have. It takes each order and uses an analytic function to work out the next order date - if the gap is greater than 30 days then a lapse is recorded
WITH
cust_orders (customer_id , order_date , order_id )
AS
(SELECT 1, TO_DATE('01/01/2016','DD/MM/YYYY'), 1001 FROM dual UNION ALL
SELECT 1, TO_DATE('29/01/2016','DD/MM/YYYY'), 1002 FROM dual UNION ALL
SELECT 1, TO_DATE('01/03/2016','DD/MM/YYYY'), 1003 FROM dual UNION ALL
SELECT 2, TO_DATE('01/01/2016','DD/MM/YYYY'), 1004 FROM dual UNION ALL
SELECT 2, TO_DATE('29/01/2016','DD/MM/YYYY'), 1005 FROM dual UNION ALL
SELECT 2, TO_DATE('01/04/2016','DD/MM/YYYY'), 1006 FROM dual UNION ALL
SELECT 2, TO_DATE('01/06/2016','DD/MM/YYYY'), 1007 FROM dual UNION ALL
SELECT 2, TO_DATE('01/08/2016','DD/MM/YYYY'), 1008 FROM dual UNION ALL
SELECT 3, TO_DATE('01/09/2016','DD/MM/YYYY'), 1009 FROM dual UNION ALL
SELECT 3, TO_DATE('01/12/2016','DD/MM/YYYY'), 1010 FROM dual UNION ALL
SELECT 3, TO_DATE('02/12/2016','DD/MM/YYYY'), 1011 FROM dual UNION ALL
SELECT 3, TO_DATE('03/12/2016','DD/MM/YYYY'), 1012 FROM dual UNION ALL
SELECT 3, TO_DATE('04/12/2016','DD/MM/YYYY'), 1013 FROM dual UNION ALL
SELECT 3, TO_DATE('05/12/2016','DD/MM/YYYY'), 1014 FROM dual UNION ALL
SELECT 3, TO_DATE('06/12/2016','DD/MM/YYYY'), 1015 FROM dual UNION ALL
SELECT 3, TO_DATE('07/12/2016','DD/MM/YYYY'), 1016 FROM dual
)
SELECT
customer_id
,order_date
,order_id
,next_order_date
,order_date + 30 lapse_date
FROM
(SELECT
customer_id
,order_date
,order_id
,LEAD(order_date) OVER (PARTITION BY customer_id ORDER BY order_date) next_order_date
FROM
cust_orders
)
WHERE NVL(next_order_date,sysdate) - order_date > 30
;
Now join that to a set of dates and run a COUNT function (enter the year parameter as YYYY) :
WITH
cust_orders (customer_id , order_date , order_id )
AS
(SELECT 1, TO_DATE('01/01/2016','DD/MM/YYYY'), 1001 FROM dual UNION ALL
SELECT 1, TO_DATE('29/01/2016','DD/MM/YYYY'), 1002 FROM dual UNION ALL
SELECT 1, TO_DATE('01/03/2016','DD/MM/YYYY'), 1003 FROM dual UNION ALL
SELECT 2, TO_DATE('01/01/2016','DD/MM/YYYY'), 1004 FROM dual UNION ALL
SELECT 2, TO_DATE('29/01/2016','DD/MM/YYYY'), 1005 FROM dual UNION ALL
SELECT 2, TO_DATE('01/04/2016','DD/MM/YYYY'), 1006 FROM dual UNION ALL
SELECT 2, TO_DATE('01/06/2016','DD/MM/YYYY'), 1007 FROM dual UNION ALL
SELECT 2, TO_DATE('01/08/2016','DD/MM/YYYY'), 1008 FROM dual UNION ALL
SELECT 3, TO_DATE('01/09/2016','DD/MM/YYYY'), 1009 FROM dual UNION ALL
SELECT 3, TO_DATE('01/12/2016','DD/MM/YYYY'), 1010 FROM dual UNION ALL
SELECT 3, TO_DATE('02/12/2016','DD/MM/YYYY'), 1011 FROM dual UNION ALL
SELECT 3, TO_DATE('03/12/2016','DD/MM/YYYY'), 1012 FROM dual UNION ALL
SELECT 3, TO_DATE('04/12/2016','DD/MM/YYYY'), 1013 FROM dual UNION ALL
SELECT 3, TO_DATE('05/12/2016','DD/MM/YYYY'), 1014 FROM dual UNION ALL
SELECT 3, TO_DATE('06/12/2016','DD/MM/YYYY'), 1015 FROM dual UNION ALL
SELECT 3, TO_DATE('07/12/2016','DD/MM/YYYY'), 1016 FROM dual
)
,calendar (date_value)
AS
(SELECT TO_DATE('01/01/'||:P_year,'DD/MM/YYYY') + (rownum -1)
FROM all_tables
WHERE rownum < (TO_DATE('31/12/'||:P_year,'DD/MM/YYYY') - TO_DATE('01/01/'||:P_year,'DD/MM/YYYY')) + 2
)
SELECT
calendar.date_value
,COUNT(*)
FROM
(
SELECT
customer_id
,order_date
,order_id
,next_order_date
,order_date + 30 lapse_date
FROM
(SELECT
customer_id
,order_date
,order_id
,LEAD(order_date) OVER (PARTITION BY customer_id ORDER BY order_date) next_order_date
FROM
cust_orders
)
WHERE NVL(next_order_date,sysdate) - order_date > 30
) lapses
,calendar
WHERE 1=1
AND calendar.date_value = TRUNC(lapses.lapse_date)
GROUP BY
calendar.date_value
;
Or if you really want every date printed out then use this :
WITH
cust_orders (customer_id , order_date , order_id )
AS
(SELECT 1, TO_DATE('01/01/2016','DD/MM/YYYY'), 1001 FROM dual UNION ALL
SELECT 1, TO_DATE('29/01/2016','DD/MM/YYYY'), 1002 FROM dual UNION ALL
SELECT 1, TO_DATE('01/03/2016','DD/MM/YYYY'), 1003 FROM dual UNION ALL
SELECT 2, TO_DATE('01/01/2016','DD/MM/YYYY'), 1004 FROM dual UNION ALL
SELECT 2, TO_DATE('29/01/2016','DD/MM/YYYY'), 1005 FROM dual UNION ALL
SELECT 2, TO_DATE('01/04/2016','DD/MM/YYYY'), 1006 FROM dual UNION ALL
SELECT 2, TO_DATE('01/06/2016','DD/MM/YYYY'), 1007 FROM dual UNION ALL
SELECT 2, TO_DATE('01/08/2016','DD/MM/YYYY'), 1008 FROM dual UNION ALL
SELECT 3, TO_DATE('01/09/2016','DD/MM/YYYY'), 1009 FROM dual UNION ALL
SELECT 3, TO_DATE('01/12/2016','DD/MM/YYYY'), 1010 FROM dual UNION ALL
SELECT 3, TO_DATE('02/12/2016','DD/MM/YYYY'), 1011 FROM dual UNION ALL
SELECT 3, TO_DATE('03/12/2016','DD/MM/YYYY'), 1012 FROM dual UNION ALL
SELECT 3, TO_DATE('04/12/2016','DD/MM/YYYY'), 1013 FROM dual UNION ALL
SELECT 3, TO_DATE('05/12/2016','DD/MM/YYYY'), 1014 FROM dual UNION ALL
SELECT 3, TO_DATE('06/12/2016','DD/MM/YYYY'), 1015 FROM dual UNION ALL
SELECT 3, TO_DATE('07/12/2016','DD/MM/YYYY'), 1016 FROM dual
)
,lapses
AS
(SELECT
customer_id
,order_date
,order_id
,next_order_date
,order_date + 30 lapse_date
FROM
(SELECT
customer_id
,order_date
,order_id
,LEAD(order_date) OVER (PARTITION BY customer_id ORDER BY order_date) next_order_date
FROM
cust_orders
)
WHERE NVL(next_order_date,sysdate) - order_date > 30
)
,calendar (date_value)
AS
(SELECT TO_DATE('01/01/'||:P_year,'DD/MM/YYYY') + (rownum -1)
FROM all_tables
WHERE rownum < (TO_DATE('31/12/'||:P_year,'DD/MM/YYYY') - TO_DATE('01/01/'||:P_year,'DD/MM/YYYY')) + 2
)
SELECT
calendar.date_value
,(SELECT COUNT(*)
FROM lapses
WHERE calendar.date_value = lapses.lapse_date
)
FROM
calendar
WHERE 1=1
ORDER BY
calendar.date_value
;
Here's how I'd do it:
WITH your_table AS (SELECT 123 customer_id, to_date('24/01/2016', 'dd/mm/yyyy') order_date, 12345 order_id FROM dual UNION ALL
SELECT 123 customer_id, to_date('24/01/2016', 'dd/mm/yyyy') order_date, 12346 order_id FROM dual UNION ALL
SELECT 123 customer_id, to_date('25/01/2016', 'dd/mm/yyyy') order_date, 12347 order_id FROM dual UNION ALL
SELECT 123 customer_id, to_date('24/02/2016', 'dd/mm/yyyy') order_date, 12347 order_id FROM dual UNION ALL
SELECT 123 customer_id, to_date('16/03/2016', 'dd/mm/yyyy') order_date, 12348 order_id FROM dual UNION ALL
SELECT 123 customer_id, to_date('18/04/2016', 'dd/mm/yyyy') order_date, 12349 order_id FROM dual UNION ALL
SELECT 456 customer_id, to_date('20/02/2016', 'dd/mm/yyyy') order_date, 12350 order_id FROM dual UNION ALL
SELECT 456 customer_id, to_date('01/03/2016', 'dd/mm/yyyy') order_date, 12351 order_id FROM dual UNION ALL
SELECT 456 customer_id, to_date('03/03/2016', 'dd/mm/yyyy') order_date, 12352 order_id FROM dual UNION ALL
SELECT 456 customer_id, to_date('18/04/2016', 'dd/mm/yyyy') order_date, 12353 order_id FROM dual UNION ALL
SELECT 456 customer_id, to_date('20/05/2016', 'dd/mm/yyyy') order_date, 12354 order_id FROM dual UNION ALL
SELECT 456 customer_id, to_date('23/06/2016', 'dd/mm/yyyy') order_date, 12355 order_id FROM dual UNION ALL
SELECT 456 customer_id, to_date('19/01/2017', 'dd/mm/yyyy') order_date, 12356 order_id FROM dual),
-- end of mimicking your_table with data in it
lapsed_info AS (SELECT customer_id,
order_date,
CASE WHEN TRUNC(SYSDATE) - order_date <= 30 THEN NULL
WHEN COUNT(*) OVER (PARTITION BY customer_id ORDER BY order_date RANGE BETWEEN 1 FOLLOWING AND 30 FOLLOWING) = 0 THEN order_date+30
ELSE NULL
END lapsed_date
FROM your_table),
dates AS (SELECT to_date('01/01/2016', 'dd/mm/yyyy') + LEVEL -1 dt
FROM dual
CONNECT BY to_date('01/01/2016', 'dd/mm/yyyy') + LEVEL -1 <= TRUNC(SYSDATE))
SELECT dates.dt,
COUNT(li.lapsed_date) lapsed_count
FROM dates
LEFT OUTER JOIN lapsed_info li ON dates.dt = li.lapsed_date
GROUP BY dates.dt
ORDER BY dates.dt;
Results:
DT LAPSED_COUNT
---------- ------------
01/01/2016 0
<snip>
23/01/2016 0
24/01/2016 0
25/01/2016 0
26/01/2016 0
<snip>
19/02/2016 0
20/02/2016 0
21/02/2016 0
22/02/2016 0
23/02/2016 0
24/02/2016 1
25/02/2016 0
<snip>
29/02/2016 0
01/03/2016 0
02/03/2016 0
03/03/2016 0
04/03/2016 0
<snip>
15/03/2016 0
16/03/2016 0
17/03/2016 0
<snip>
20/03/2016 0
21/03/2016 0
22/03/2016 0
<snip>
30/03/2016 0
31/03/2016 0
01/04/2016 0
02/04/2016 1
03/04/2016 0
<snip>
14/04/2016 0
15/04/2016 1
16/04/2016 0
17/04/2016 0
18/04/2016 0
19/04/2016 0
<snip>
17/05/2016 0
18/05/2016 2
19/05/2016 0
20/05/2016 0
21/05/2016 0
<snip>
18/06/2016 0
19/06/2016 1
20/06/2016 0
21/06/2016 0
22/06/2016 0
23/06/2016 0
24/06/2016 0
<snip>
22/07/2016 0
23/07/2016 1
24/07/2016 0
<snip>
18/01/2017 0
19/01/2017 0
20/01/2017 0
<snip>
08/02/2017 0
This takes your data, and uses an the analytic count function to work out the number of rows that have a value within 30 days of (but excluding) the current row's date.
Then we apply a case expression to determine that if the row has a date within 30 days of today's date, we'll count those as not lapsed. If a count of 0 was returned, then the row is considered lapsed and we'll output the lapsed date as the order_date plus 30 days. Any other count result means the row has not lapsed.
The above is all worked out in the lapsed_info subquery.
Then all we need to do is list the dates (see the dates subquery) and outer join the lapsed_info subquery to it based on the lapsed_date and then do a count of the lapsed dates for each day.