sql query need to check date along with Store_id - sql

I have two tables, both has millions of rows.
Table A:-
Store_id, Purchase_dt, Amount
-------- ----------- ------
1001 02JAN19 12.20
1001 05MAY20 13.30
1002 07JUL21 10.97
Table B:-
Store_id, Valid_from, Valid_to, Profile_ID
-------- ---------- -------- ----------
1001 01JAN17 08JUL19 56
1001 09JUL19 12DEC99 60
1002 01JAN20 12DEC99 70
I need to find only transaction from stores that has a profile id of 60 and 70 and Purchase_dt should be between Valid_from and valid_to and for this joining column is Store_id.
Target table expectation is:
Store_id, Purchase_dt, Amount, Profile_ID
-------- ----------- ------
1001 05MAY20 13.30 60
1002 07JUL21 10.97 70
I tried with
Select
a.Store_id,
a.Purchase_dt,
a.Amount,
b.Profile_ID
from
table_a a,
table_b b
where
a.Store_id = b.Store_id
and
a.Purchase_dt between b.Valid_from and b.Valid_to
and
b.Profile_ID in (60,70)
but not getting the desired result, all dates are date data type any help is appreciated!

If dates are really stored as strings (that's what sample data you posted looks like), then - if you want between to work properly - you first have to convert these strings into valid DATE datatype values (using to_date function with appropriate format model).
Moreover, you're looking for trouble keeping 2-digits years; didn't Y2K bug teach you anything?
I'd suggest you to keep dates in DATE datatype columns and avoid many kinds of problems.
As of your current problem, here you are:
Sample data:
SQL> with
2 table_a (store_id, purchase_dt, amount) as
3 (select 1001, '02JAN19', 12.20 from dual union all
4 select 1001, '05MAY20', 13.30 from dual union all
5 select 1002, '07JUL21', 10.97 from dual
6 ),
7 table_b (store_id, valid_from, valid_to, profile_id) as
8 (select 1001, '01JAN17', '08JUL19', 56 from dual union all
9 select 1001, '09JUL19', '12DEC99', 60 from dual union all
10 select 1002, '01JAN20', '12DEC99', 70 from dual
11 )
Query begins here:
12 select a.store_id, a.purchase_dt, a.amount, b.profile_id
13 from table_a a join table_b b
14 on a.store_id = b.store_id
15 and to_date(a.purchase_dt, 'ddMONyy') between
16 to_date(b.valid_from, 'ddMONyy') and to_date(b.valid_to, 'ddMONyy')
17 where b.profile_id in (60, 70);
STORE_ID PURCHAS AMOUNT PROFILE_ID
---------- ------- ---------- ----------
1001 05MAY20 13,3 60
1002 07JUL21 10,97 70
SQL>
If - as you commented - date values really are DATEs - then it gets simpler.
Compare:
Strings:
15 and to_date(a.purchase_dt, 'ddMONyy') between
16 to_date(b.valid_from, 'ddMONyy') and
to_date(b.valid_to, 'ddMONyy')
Dates:
15 and a.purchase_dt between b.valid_from and b.valid_to
The whole query that deals with DATE datatype:
SQL> with
2 table_a (store_id, purchase_dt, amount) as
3 (select 1001, date '2019-01-02', 12.20 from dual union all
4 select 1001, date '2020-05-05', 13.30 from dual union all
5 select 1002, date '2021-07-07', 10.97 from dual
6 ),
7 table_b (store_id, valid_from, valid_to, profile_id) as
8 (select 1001, date '2017-01-01', date '2019-07-08', 56 from dual union all
9 select 1001, date '2019-07-09', date '2099-12-12', 60 from dual union all
10 select 1002, date '2020-01-01', date '2099-12-12', 70 from dual
11 )
12 select a.store_id, a.purchase_dt, a.amount, b.profile_id
13 from table_a a join table_b b
14 on a.store_id = b.store_id
15 and a.purchase_dt between b.valid_from and b.valid_to
16 where b.profile_id in (60, 70) ;
STORE_ID PURCHASE AMOUNT PROFILE_ID
---------- -------- ---------- ----------
1001 05.05.20 13,3 60
1002 07.07.21 10,97 70
SQL>
Your query applied to same sample data also works:
SQL> with
2 table_a (store_id, purchase_dt, amount) as
3 (select 1001, date '2019-01-02', 12.20 from dual union all
4 select 1001, date '2020-05-05', 13.30 from dual union all
5 select 1002, date '2021-07-07', 10.97 from dual
6 ),
7 table_b (store_id, valid_from, valid_to, profile_id) as
8 (select 1001, date '2017-01-01', date '2019-07-08', 56 from dual union all
9 select 1001, date '2019-07-09', date '2099-12-12', 60 from dual union all
10 select 1002, date '2020-01-01', date '2099-12-12', 70 from dual
11 )
This is your query:
12 Select
13 a.Store_id,
14 a.Purchase_dt,
15 a.Amount,
16 b.Profile_ID
17 from
18 table_a a,
19 table_b b
20 where
21 a.Store_id = b.Store_id
22 and
23 a.Purchase_dt between b.Valid_from and b.Valid_to
24 and
25 b.Profile_ID in (60,70);
STORE_ID PURCHASE AMOUNT PROFILE_ID
---------- -------- ---------- ----------
1001 05.05.20 13,3 60
1002 07.07.21 10,97 70
SQL>

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

Need row data column wise in oracle

I need to compare prices coming from 3 sources (27, 2, 55) from owners column. i am not sure if i should use select sub query or left join ?
Desired Result
Looks like a pivot.
Sample data in lines #1 - 10; query you might need begins at line #12.
SQL> with test (id_value, as_of, timezone, price, owner) as
2 -- sample data
3 (select 'EEM.A', date '2021-06-25', 'J1530', 55.04, 55 from dual union all
4 select 'EEM.A', date '2021-06-25', 'J1530', 55.04, 27 from dual union all
5 select 'EEM.A', date '2021-06-25', 'J1530', 55.04, 2 from dual union all
6 --
7 select 'AMX.N', date '2021-06-25', 'J1530', 15.4, 55 from dual union all
8 select 'AMX.N', date '2021-06-25', 'J1530', 15.4, 27 from dual union all
9 select 'AMX.N', date '2021-06-25', 'J1530', 15.4, 2 from dual
10 )
11 -- query begins here
12 select *
13 from test
14 pivot (max(price)
15 for owner in (55, 27, 2)
16 );
ID_VA AS_OF TIMEZ 55 27 2
----- ---------- ----- ---------- ---------- ----------
AMX.N 06/25/2021 J1530 15,4 15,4 15,4
EEM.A 06/25/2021 J1530 55,04 55,04 55,04
SQL>

How can update a column based on the value of another column in SQL?

Basically I have Product table like this:
date price
--------- -----
02-SEP-14 50
03-SEP-14 60
04-SEP-14 60
05-SEP-14 60
07-SEP-14 71
08-SEP-14 45
09-SEP-14 45
10-SEP-14 24
11-SEP-14 60
I need to update the table in this form
date price id
--------- ----- --
02-SEP-14 50 1
03-SEP-14 60 2
04-SEP-14 60 2
05-SEP-14 60 2
07-SEP-14 71 3
08-SEP-14 45 4
09-SEP-14 45 4
10-SEP-14 24 5
11-SEP-14 60 6
What I have tried:
CREATE SEQUENCE user_id_seq
START WITH 1
INCREMENT BY 1
CACHE 20;
ALTER TABLE Product
ADD (ID number);
UPDATE Product SET ID = user_id_seq.nextval;
This is updating the ID in the usual way like 1,2,3,4,5..
I have no idea how to do it using basic SQL commands. Please suggest how can I make it. Thank you in advance.
Here is one way to create a view from your base data. I assume you have more than one product (identified by product id), and that the price dates aren't necessarily consecutive. The sequence is separate for each product id. (Also, product should be the name of a different table - where the product id is primary key, and you have other information such as product name, category, etc. The table in your post would be more properly called something like price_history.)
alter session set nls_date_format='dd-MON-rr';
create table product ( prod_id number, dt date, price number );
insert into product ( prod_id, dt, price )
select 101, '02-SEP-14', 50 from dual union all
select 101, '03-SEP-14', 60 from dual union all
select 101, '04-SEP-14', 60 from dual union all
select 101, '05-SEP-14', 60 from dual union all
select 101, '07-SEP-14', 71 from dual union all
select 101, '08-SEP-14', 45 from dual union all
select 101, '09-SEP-14', 45 from dual union all
select 101, '10-SEP-14', 24 from dual union all
select 101, '11-SEP-14', 60 from dual union all
select 102, '02-SEP-14', 45 from dual union all
select 102, '04-SEP-14', 45 from dual union all
select 102, '05-SEP-14', 60 from dual union all
select 102, '06-SEP-14', 50 from dual union all
select 102, '09-SEP-14', 60 from dual
;
commit;
create view product_vw ( prod_id, dt, price, seq ) as
select prod_id, dt, price,
count(flag) over (partition by prod_id order by dt)
from ( select prod_id, dt, price,
case when price = lag(price) over (partition by prod_id order by dt)
then null else 1 end as flag
from product
)
;
Now check what the view looks like:
select * from product_vw;
PROD_ID DT PRICE SEQ
------- ------------------- ---------- ----------
101 02/09/0014 00:00:00 50 1
101 03/09/0014 00:00:00 60 2
101 04/09/0014 00:00:00 60 2
101 05/09/0014 00:00:00 60 2
101 07/09/0014 00:00:00 71 3
101 08/09/0014 00:00:00 45 4
101 09/09/0014 00:00:00 45 4
101 10/09/0014 00:00:00 24 5
101 11/09/0014 00:00:00 60 6
102 02/09/0014 00:00:00 45 1
102 04/09/0014 00:00:00 45 1
102 05/09/0014 00:00:00 60 2
102 06/09/0014 00:00:00 50 3
102 09/09/0014 00:00:00 60 4
NOTE: This answers the question that was originally asked. The OP changed the data.
If your data is not too large, you can use a correlated subquery:
update product p
set id = (select count(distinct p2.price)
from product p2
where p2.date <= p.date
);
If your data is larger, then merge is more appropriate.
WITH cts AS
(
SELECT row_number() over (partition by price order by price ) as id
,date
,price
FROM Product
)
UPDATE p
set p.id = cts.id
from product p join cts on cts.id = p.id
This is the best way by which you try to do.
There is no another simple way to do this using simple statements

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.

Finding dates when accounts reach zero

Thanks for taking the time to examine my issue.
I'm trying to figure out a way to return dates when an account reaches 0
Sample data:
DATE ACCOUNT AMOUNT
11/01 001 100
11/02 002 50
11/03 001 -100
11/07 001 20
11/15 002 -50
11/20 001 -20
Wanted results:
Account ZeroDate
001 11/03
002 11/15
001 11/20
So far I haven't been able to figure out anything that works. Might you be able to point me in the right direction?
Thanks again in advance!
You can use analytic functions to compute the running balance
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select date '2011-11-01' dt, 1 account, 100 amt from dual union all
3 select date '2011-11-02', 2, 50 from dual union all
4 select date '2011-11-03', 1, -100 from dual union all
5 select date '2011-11-07', 1, 20 from dual union all
6 select date '2011-11-15', 2, -50 from dual union all
7 select date '2011-11-20', 1, -20 from dual
8 )
9 select dt,
10 account,
11 amt,
12 sum(amt) over (partition by account order by dt) current_balance
13* from x
SQL> /
DT ACCOUNT AMT CURRENT_BALANCE
--------- ---------- ---------- ---------------
01-NOV-11 1 100 100
03-NOV-11 1 -100 0
07-NOV-11 1 20 20
20-NOV-11 1 -20 0
02-NOV-11 2 50 50
15-NOV-11 2 -50 0
6 rows selected.
and then use the running balance to find the zero dates.
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select date '2011-11-01' dt, 1 account, 100 amt from dual union all
3 select date '2011-11-02', 2, 50 from dual union all
4 select date '2011-11-03', 1, -100 from dual union all
5 select date '2011-11-07', 1, 20 from dual union all
6 select date '2011-11-15', 2, -50 from dual union all
7 select date '2011-11-20', 1, -20 from dual
8 )
9 select account,
10 dt zero_date
11 from (
12 select dt,
13 account,
14 amt,
15 sum(amt) over (partition by account order by dt) current_balance
16 from x
17 )
18* where current_balance = 0
SQL> /
ACCOUNT ZERO_DATE
---------- ---------
1 03-NOV-11
1 20-NOV-11
2 15-NOV-11
create table myacct (dt varchar2(5)
, account varchar2(3)
, amount number
)
;
insert into myacct values ('11/01', '001', 100);
insert into myacct values ('11/02', '002', 50);
insert into myacct values ('11/03', '001', -100);
insert into myacct values ('11/07', '001', 20);
insert into myacct values ('11/15', '002', -50);
insert into myacct values ('11/20', '001', -20);
commit;
/* results wanted:
Account ZeroDate
001 11/03
002 11/15
001 11/20 */
select account "Account", dt "ZeroDate"
from myacct
where amount <= 0
;
/* results from above query:
Account ZeroDate
001 11/03
002 11/15
001 11/20
*/