I have a written a query which is giving following output
But my actual need is something different
TRANSACTION_DATE DETAILS LEAVE_CREDITED LEAVE_DEBITED
29-Sep-2012 Sep-2012-Sep-2012 0.11
01-Oct-2012 Oct-2012-Dec-2012 2.5
01-Jan-2013 Jan-2013-Mar-2013 2.5
31-Mar-2013 LAPSE - 540007 1.9
01-Apr-2013 Apr-2013-Jun-2013 2.5
30-Apr-2013 Lev_102935703 0.11
There should be a 5th column such that
Its value should be LASTBALANCE+(Leave_Credited)-(Leave_Debited)
In this case
BALANCE
0.11-0= 0.11
0.11+(2.5-0)= 2.61
2.61+(2.5-0)= 5.11
5.11+(0-1.9)= 3.02
Please help.
My query is something like
SELECT TRUNC(NVL(C.UPDATED_DATE, C.CREATED_DATE)) TRANSACTION_DATE,
TO_CHAR(C.SERVICE_START_DATE, 'Mon-YYYY') || '-' ||
TO_CHAR(C.SERVICE_END_DATE, 'Mon-YYYY') Details,
C.LEAVE_CREDITED,
NULL LEAVE_DEBITED
FROM LEAVE.GES_LEV_CREDIT_DETAILS C, LEAVE.GES_LEV_CREDIT_MASTER CM
WHERE C.LEV_CREDIT_ID = CM.LEV_CREDIT_ID
AND C.PERSON_ID = 12345
AND CM.COUNTRY_LEAVE_TYPE_ID = 5225
AND c.leave_credited<>0
UNION
SELECT TRUNC(NVL(d.UPDATED_DATE, d.CREATED_DATE)) TRANSACTION_DATE,
d.reference,
NULL,
d.no_of_days LEAVE_DEBITED
FROM LEAVE.GES_LEV_CREDIT_DETAILS C,
LEAVE.GES_LEV_CREDIT_MASTER CM,
leave.ges_lev_debit_req_dtls D
WHERE C.LEV_CREDIT_ID = CM.LEV_CREDIT_ID
AND C.LEV_CREDIT_DETAIL_ID = D.LEV_CREDIT_DETL_ID
AND C.PERSON_ID = 12345
AND CM.COUNTRY_LEAVE_TYPE_ID = 5225
5.11+(0-1.9)= 3.02
Shouldn't it be 3.21.
Use Analytic SUM() OVER() for both credited and debited columns and then take the difference of them.
Let's see a working test case, I have built your table using WITH clause, in reality you just need to use your table instead of DATA:
SQL> WITH DATA AS(
2 SELECT to_date('29-Sep-2012', 'dd-Mon-yyyy') TRANSACTION_DATE, 0.11 LEAVE_CREDITED, NULL LEAVE_DEBITED FROM dual UNION ALL
3 SELECT to_date('01-Oct-2012', 'dd-Mon-yyyy') TRANSACTION_DATE, 2.5 LEAVE_CREDITED, NULL LEAVE_DEBITED FROM dual UNION ALL
4 SELECT to_date('01-Jan-2013', 'dd-Mon-yyyy') TRANSACTION_DATE, 2.5 LEAVE_CREDITED, NULL LEAVE_DEBITED FROM dual UNION ALL
5 SELECT to_date('31-Mar-2013', 'dd-Mon-yyyy') TRANSACTION_DATE, NULL LEAVE_CREDITED, 1.9 LEAVE_DEBITED FROM dual UNION ALL
6 SELECT to_date('01-Apr-2013', 'dd-Mon-yyyy') TRANSACTION_DATE, 2.5 LEAVE_CREDITED, NULL LEAVE_DEBITED FROM dual UNION ALL
7 SELECT to_date('30-Apr-2013', 'dd-Mon-yyyy') TRANSACTION_DATE, null LEAVE_CREDITED, 0.11 LEAVE_DEBITED FROM dual
8 )
9 SELECT t.*,
10 SUM(NVL(leave_credited,0)) OVER(ORDER BY TRANSACTION_DATE)
11 -
12 SUM(NVL(LEAVE_DEBITED,0)) OVER(ORDER BY TRANSACTION_DATE) LASTBALANCE
13 FROM DATA t
14 /
TRANSACTI LEAVE_CREDITED LEAVE_DEBITED LASTBALANCE
--------- -------------- ------------- -----------
29-SEP-12 .11 .11
01-OCT-12 2.5 2.61
01-JAN-13 2.5 5.11
31-MAR-13 1.9 3.21
01-APR-13 2.5 5.71
30-APR-13 .11 5.6
6 rows selected.
SQL>
Your query would look like:
SELECT t.*,
SUM(NVL(leave_credited,0)) OVER(ORDER BY TRANSACTION_DATE)
-
SUM(NVL(LEAVE_DEBITED,0)) OVER(ORDER BY TRANSACTION_DATE) LASTBALANCE
FROM table_name t
/
I am using Oracle.
You can use sum() over (order by transaction_date) to get a running
total - which will handle the lapsed leaves etc. ->
Output:
Script:
select to_date(transaction_date) transaction_date, details,
leave_credited, leave_debited,
sum(leave_credited - leave_debited) over (order by to_date(transaction_date) asc) final_balance
from
(select '29-Sep-2012' transaction_date,
'Sep-2012-Sep-2012' details,
0.11 leave_credited,
0 leave_debited
from dual
union all
select '01-Oct-2012' , 'Oct-2012-Dec-2012' , 2.5 , 0 from dual
union all
select '01-Jan-2013' , 'Jan-2013-Mar-2013' , 2.5 , 0 from dual
union all
select '31-Mar-2013' , 'LAPSE - 540007' , 0, 1.9 from dual
union all
select '01-apr-2013' , 'apr-2013-jun-2013' , 2.5, 0 from dual
union all
select '30-Apr-2013' , 'Lev_102935703' , 0, 0.11 from dual
);
Related
I have a problem how to calculate the days how many days has passed since previous order.
My code:
select
order_id,
order_date
from
oe.orders
where customer_id = 838
order by
order_date desc
The order_id and order_date are like below:
order_id = 1920 & order_date= 25-MAR-19 15.45.38.000000000
order_id = 1618 & order_date= 08-FEB-19 12.51.39.000000000
order_id = 1592 & order_date= 04-FEB-19 07.35.46.000000000
...
I am new user of sql and no idea how to do it. Thank you for your help!
If you want the differences in days (just the date part) then:
WITH
tbl AS
(
Select 1 "ID", To_Date('25-MAR-19 15.45.38', 'dd-MON-yy hh24:mi:ss') "A_DATE" From Dual Union All
Select 2 "ID", To_Date('08-FEB-19 12.51.39', 'dd-MON-yy hh24:mi:ss') "A_DATE" From Dual Union All
Select 3 "ID", To_Date('04-FEB-19 07.35.46', 'dd-MON-yy hh24:mi:ss') "A_DATE" From Dual Union All
Select 4 "ID", To_Date('28-JAN-19 12.13.10', 'dd-MON-yy hh24:mi:ss') "A_DATE" From Dual
)
Select
ID "ID",
TRUNC(A_DATE, 'dd') - TRUNC(Nvl(First_Value(A_DATE) OVER (Order By ID Rows Between 1 Preceding And Current Row), A_DATE), 'dd') "DAYS_DIFF"
From
tbl
ID
DAYS_DIFF
1
0
2
-45
3
-4
4
-7
OR ...
Select
ID "ID",
TRUNC(A_DATE, 'dd') - TRUNC(Nvl(Last_Value(A_DATE) OVER (Order By ID Rows Between Current Row And 1 Following ), A_DATE), 'dd') "DAYS_DIFF"
From
tbl
Order By TRUNC(A_DATE, 'dd')
... result
ID
DAYS_DIFF
4
0
3
7
2
4
1
45
Regards
CREATE TABLE orders
(ORDER_ID, ORDER_DATE) AS
SELECT 3, TIMESTAMP'2022-10-31 09:54:48' FROM DUAL UNION ALL
SELECT 2, TIMESTAMP'2022-10-17 19:04:44' FROM DUAL UNION ALL
SELECT 1, TIMESTAMP'2022-10-08 14:44:23' FROM DUAL
SELECT order_id, order_date,
order_date - LAG(order_date) OVER (ORDER BY order_id) AS diff
FROM orders;
ORDER_ID ORDER_DATE DIFF
1 08-OCT-22 02.44.23.000000 PM -
2 17-OCT-22 07.04.44.000000 PM +000000009 04:20:21.000000000
3 31-OCT-22 09.54.48.000000 AM +000000013 14:50:04.000000000
I just wanted to know, how can I reset summation of number field in below SQL query.
Attached screenshot is the sample of result that I need to get.
Query used:
SUM(UNPAID_MONTHLY) OVER(PARTITION BY SAMPLE_ACCT ORDER BY MONTH_NO DESC) TOTAL_UNPAID_AMT
You need to reset the sum every time the value is zero. You can use cumulative sum to define the group and then another cumulative sum:
select t.*,
sum(unpaid_monthly) over (partition by sample_acct, grp order by month_no desc)
from (select t.*,
sum(case when unpaid_monthly = 0 then 1 else 0 end) over (partition by sample_acct order by month_no) as grp
from t
) t;
You can also use the MATCH_RECOGNIZE clause (if you run Oracle 12 or higher):
WITH t (unpaid_monthly, sample_acct, month_no) AS
(SELECT 1335.67, 22900005, 1 FROM dual UNION ALL
SELECT 1289.36, 22900005, 2 FROM dual UNION ALL
SELECT 1241.95, 22900005, 3 FROM dual UNION ALL
SELECT 1211.32, 22900005, 4 FROM dual UNION ALL
SELECT 1179.33, 22900005, 5 FROM dual UNION ALL
SELECT 0, 22900005, 6 FROM dual UNION ALL
SELECT 5509.8, 22900005, 7 FROM dual UNION ALL
SELECT 3388.59, 22900005, 8 FROM dual UNION ALL
SELECT 1398.41, 22900005, 9 FROM dual UNION ALL
SELECT 0, 22900005, 10 FROM dual UNION ALL
SELECT 1717.97, 22900005, 11 FROM dual UNION ALL
SELECT 0, 22900005, 12 FROM dual UNION ALL
SELECT 5016.4, 22900005, 13 FROM dual)
SELECT unpaid_monthly, sample_acct, month_no,
sum_unpaid + unpaid_monthly AS TOTAL_UNPAID_AMT
FROM t
MATCH_RECOGNIZE (
PARTITION BY sample_acct
ORDER BY month_no
MEASURES
FINAL SUM(unpaid_monthly) - SUM(unpaid_monthly) AS sum_unpaid
ALL ROWS PER MATCH
PATTERN (a+ b?)
DEFINE
a AS unpaid_monthly > 0);
UNPAID_MONTHLY SAMPLE_ACCT MONTH_NO TOTAL_UNPAID_AMT
=============================================================
1335.67 22900005 1 6257.63
1289.36 22900005 2 4921.96
1241.95 22900005 3 3632.6
1211.32 22900005 4 2390.65
1179.33 22900005 5 1179.33
0 22900005 6 0
5509.8 22900005 7 10296.8
3388.59 22900005 8 4787
1398.41 22900005 9 1398.41
0 22900005 10 0
1717.97 22900005 11 1717.97
0 22900005 12 0
5016.4 22900005 13 5016.4
My table MEASUREMENTS (Oracle SQL 12) has 3 columns: DT - measurement timestamp, MEASUREMENT - value, THRESHOLD - upper limit.
Sometimes measurements are above the threshold. Trying to calculate time periods when the measurement value was higher than the threshold.
DT | MEASUREMENT | THRESHOLD
---------------+-------------+--------------------
04.08.16 01:10 | 60,5 | 70,0
04.08.16 01:20 | 65,5 | 70,0
04.08.16 01:30 | 68,1 | 70,0
04.08.16 01:40 | 70,1* | 70,0 //period start
04.08.16 01:50 | 70,1* | 70,0
04.08.16 02:00 | 70,75* | 70,0 //period end
04.08.16 02:10 | 53,5 | 70,0
04.08.16 02:20 | 50,15 | 70,0
04.08.16 02:30 | 52,15 | 70,0
04.08.16 02:40 | 53,15 | 70,0
Expected result (02:00-01:40=00:20):
DURATION | START | END
---------+----------------+---------------
00:20 | 04.08.16 01:40 | 04.08.16 02:00
You can use row_number() to identify the periods. This is a gaps-and-islands problem. The following returns each period where the measurement exceeds the threshold:
select max(dt) - min(dt) as duration, min(dt), max(dt)
from (select t.*,
row_number() over (order by dt) as seqnum,
row_number() over (partition by (case when measurement > threshold then 1 else 2 end), order by dt) as seqnum_t
from t
) t
where measurement > threshold
group by (seqnum - seqnum_t)
You can use the MATCH_RECOGNIZE clause (plus some extra info):
WITH t (DT, MEASUREMENT, THRESHOLD) AS (
SELECT TO_DATE('01:10', 'hh24:mi'), 60.5 , 70 FROM dual UNION ALL
SELECT TO_DATE('01:20', 'hh24:mi'), 65.5 , 70 FROM dual UNION ALL
SELECT TO_DATE('01:30', 'hh24:mi'), 68.1 , 70 FROM dual UNION ALL
SELECT TO_DATE('01:40', 'hh24:mi'), 70.1 , 70 FROM dual UNION ALL
SELECT TO_DATE('01:50', 'hh24:mi'), 70.1 , 70 FROM dual UNION ALL
SELECT TO_DATE('02:00', 'hh24:mi'), 70.75 , 70 FROM dual UNION ALL
SELECT TO_DATE('02:10', 'hh24:mi'), 53.5 , 70 FROM dual UNION ALL
SELECT TO_DATE('02:20', 'hh24:mi'), 50.15 , 70 FROM dual UNION ALL
SELECT TO_DATE('02:30', 'hh24:mi'), 52.15 , 70 FROM dual UNION ALL
SELECT TO_DATE('02:40', 'hh24:mi'), 53.15 , 70 FROM dual)
SELECT MEASUREMENT_MAX, match_num, FIRST_DT, LAST_DT, (LAST_DT-FIRST_DT)*24*60 AS DURATION
FROM t
MATCH_RECOGNIZE (
ORDER BY DT
MEASURES
FINAL MAX(MEASUREMENT) AS MEASUREMENT_MAX,
MATCH_NUMBER() AS match_num,
FINAL LAST(DT) AS LAST_DT,
FINAL FIRST(DT) AS FIRST_DT
PATTERN (a+)
DEFINE
a AS MEASUREMENT > THRESHOLD);
MEASUREMENT_MAX match_num FIRST_DT LAST_DT DURATION
70.75 3 01.06.2018 01:40:00 01.06.2018 02:00:00 20
You don't need to use two row_numbers, you can directly use it via cumulative approach :
select max(dt) - min(dt) as duration, min(dt), max(dt)
from (select *, row_number() over (order by dt) as seq,
sum(case when measurement > threshold then 1 else 0 end) over(order by dt) as grp
from table
) t
where measurement > threshold
group by (seq - grp);
Using row_number analytical function:
SQL> WITH measurements (DT, MEASUREMENT, THRESHOLD) AS (
2 select to_date('04.08.16 01:10', 'DD.MM.YY HH24:MI'), 60.5, 70.0 from dual union all
3 select to_date('04.08.16 01:20', 'DD.MM.YY HH24:MI'), 65.5, 70.0 from dual union all
4 select to_date('04.08.16 01:30', 'DD.MM.YY HH24:MI'), 68.1, 70.0 from dual union all
5 select to_date('04.08.16 01:40', 'DD.MM.YY HH24:MI'), 70.1, 70.0 from dual union all
6 select to_date('04.08.16 01:50', 'DD.MM.YY HH24:MI'), 70.1, 70.0 from dual union all
7 select to_date('04.08.16 02:00', 'DD.MM.YY HH24:MI'), 70.75, 70.0 from dual union all
8 select to_date('04.08.16 02:10', 'DD.MM.YY HH24:MI'), 53.5, 70.0 from dual union all
9 select to_date('04.08.16 02:20', 'DD.MM.YY HH24:MI'), 50.15, 70.0 from dual union all
10 select to_date('04.08.16 02:30', 'DD.MM.YY HH24:MI'), 52.15, 70.0 from dual union all
11 select to_date('04.08.16 02:40', 'DD.MM.YY HH24:MI'), 53.15, 70.0 from dual),
12 ---------------------
13 ---- end of data preparation
14 ---------------------
15 calculated_values AS (
16 SELECT DT,
17 MEASUREMENT,
18 THRESHOLD,
19 row_number() OVER (ORDER BY dt) - row_number() OVER (PARTITION BY CASE WHEN MEASUREMENT > THRESHOLD THEN 1 ELSE 0 END ORDER BY dt) rn,
20 CASE WHEN MEASUREMENT > THRESHOLD THEN 1 ELSE 0 END threshold_flag
21 FROM measurements)
22 SELECT cast(numtodsinterval(MAX(dt)-MIN(dt), 'DAY') AS INTERVAL DAY(0) TO SECOND(0)) AS duration,
23 MIN(dt) AS "START",
24 MAX(dt) AS "END"
25 FROM calculated_values
26 WHERE threshold_flag > 0
27 GROUP BY rn;
OUTPUT:
DURATION START END
----------- -------------------- --------------------
+0 00:20:00 8/4/2016 1:40:00 8/4/2016 2:00:00
Your query will be:
WITH calculated_values AS (
SELECT DT,
MEASUREMENT,
THRESHOLD,
row_number() OVER (ORDER BY dt) - row_number() OVER (PARTITION BY CASE WHEN MEASUREMENT > THRESHOLD THEN 1 ELSE 0 END ORDER BY dt) rn,
CASE WHEN MEASUREMENT > THRESHOLD THEN 1 ELSE 0 END threshold_flag
FROM measurements)
SELECT cast(numtodsinterval(MAX(dt)-MIN(dt), 'DAY') AS INTERVAL DAY(0) TO SECOND(0)) AS duration,
MIN(dt) AS "START",
MAX(dt) AS "END"
FROM calculated_values
WHERE threshold_flag > 0
GROUP BY rn;
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.
So let's assume I have two tables, cars and engines. Every car has an id column and sold_date column which points to a date when the car was sold. Every engine has car_id and type columns, where the first one is a foreign key to the cars table and the second is an engine's type name, which can be anything from V1 to V999.
So what I want to get is a list of dates from let's say August 1st to August 3rd with every type of engine and a number of sold cars like this:
sold_date engine_type number_of_sold_cards
08.01.2015 V8 6
08.01.2015 V6 8
08.01.2015 V4 9
08.02.2015 V8 15
08.02.2015 V6 0
08.02.2015 V4 5
08.03.2015 V8 4
08.03.2015 V6 6
08.03.2015 V4 0
The example assumes that for these 3 days were sold only cars with engines' types of V8, V6 and V4. What it means is that if there had been sold 5 types of engines for the period (V8, V6, V4, V2, V0) instead, I'd need 5 rows for every date
Partition outer join to the rescue!
with cars as (select 1 id, to_date('01/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 2 id, to_date('01/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 3 id, to_date('02/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 4 id, to_date('03/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 5 id, to_date('01/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 6 id, to_date('01/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 7 id, to_date('03/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 8 id, to_date('03/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 9 id, to_date('02/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 10 id, to_date('02/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 11 id, to_date('02/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 12 id, to_date('04/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 13 id, to_date('04/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 14 id, to_date('04/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 14 id, to_date('04/08/2015', 'dd/mm/yyyy') sold_date from dual),
engines as (select 1 car_id, 'V8' engine_type from dual union all
select 2 car_id, 'V8' engine_type from dual union all
select 3 car_id, 'V8' engine_type from dual union all
select 4 car_id, 'V8' engine_type from dual union all
select 5 car_id, 'V4' engine_type from dual union all
select 6 car_id, 'V4' engine_type from dual union all
select 7 car_id, 'V4' engine_type from dual union all
select 8 car_id, 'V4' engine_type from dual union all
select 9 car_id, 'V6' engine_type from dual union all
select 10 car_id, 'V6' engine_type from dual union all
select 11 car_id, 'V6' engine_type from dual union all
select 12 car_id, 'V6' engine_type from dual union all
select 13 car_id, 'V2' engine_type from dual union all
select 14 car_id, 'V2' engine_type from dual union all
select 15 car_id, 'V0' engine_type from dual),
engine_types as (select distinct engine_type from engines),
res as (select c.id car_id,
e.engine_type,
c.sold_date
from engines e
inner join cars c on (e.car_id = c.id)),
final_res as (select et.engine_type,
res.sold_date
from engine_types et
left outer join res on (et.engine_type = res.engine_type)),
dates as (select to_date('01/08/2015', 'dd/mm/yyyy') - 1 + level dt -- paramaterise the start date
from dual
connect by level <= to_date('03/08/2015', 'dd/mm/yyyy') - to_date('01/08/2015', 'dd/mm/yyyy') + 1 -- paramaterise the start and end dates
)
select dts.dt,
fr.engine_type,
count(fr.sold_date) cnt
from dates dts
left outer join final_res fr partition by (fr.engine_type) on (dts.dt = fr.sold_date)
group by dts.dt,
fr.engine_type
order by dts.dt,
fr.engine_type;
DT ENGINE_TYPE CNT
---------- ----------- ----------
01/08/2015 V0 0
01/08/2015 V2 0
01/08/2015 V4 2
01/08/2015 V6 0
01/08/2015 V8 2
02/08/2015 V0 0
02/08/2015 V2 0
02/08/2015 V4 0
02/08/2015 V6 3
02/08/2015 V8 1
03/08/2015 V0 0
03/08/2015 V2 0
03/08/2015 V4 2
03/08/2015 V6 0
03/08/2015 V8 1
The first and second subqueries ("cars" and "engines") are just mimicking your tables; you would not need to include them in your query.
The "engine_types" subquery is just getting the distinct list of engine_types used in the engines table. If you have some other table that lists the available engine_types, then use that instead.
The "dates" subquery is just generating a list of dates between a given date range - in an ideal world, the start and end dates would be parameterised (assuming this is being run in PL/SQL or some such).
The "res" subquery does the join on the cars and engines table, to get the type of engine sold on each date.
The "final_res" subquery outer joins the res and engine_types subqueries, so that every engine type is listed, along with the sold_date, if it was sold.
Once you have that, then it's easy to do a partition outer join on the final_res subquery to the dates subquery.
You need to join cars and engine tables. Need to count the sold cars and group by sold_date and engine_type:
select c.sold_date
,e.engine_type
,count(*) as number_of_sold_cars
from cars c
inner join engines e on c.id = e.car_id
where c.sold_date between date '2015-08-01' and date '2015-08-03'
group by c.sold_date,e.engine_type
Update:
With this query you will have all engine types sold in the selected period. You will have 0 for number_of_sold_cars if this engine type is not sold on the particular date:
with engine_type as (
select distinct e.engine_type
from cars c
inner join engines e on c.id = e.car_id
where c.sold_date between date '2015-08-01' and date '2015-08-03'
)
select c.sold_date
,t.engine_type
,count(*) as number_of_sold_cars
from cars c
inner join engines e on c.id = e.car_id
left join enginte_type t on t.engine_type = e.engine_type
where c.sold_date between date '2015-08-01' and date '2015-08-03'
group by c.sold_date,t.engine_type