Skip to Main Content

SQL & PL/SQL

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Ranking consecutive day purchases

PugzlyMay 6 2024

I have a query which is working that finds customers who have made purchases on 10+ more consecutive days. I am trying to modify the query to use DENSE_RANK() to show the greatest amount of consecutive purchases (>= 10 days) for each customer.

Unfortunately, I'm running into some syntax errors that I'm unable to resolve and would appreciate a bit of help.

Below is my test setup, test case, what I have so far and expected result. Any help would be greatly appreciated. If there is better way to achieve my results instead of with match_recognize/rank I'm certainly open to any input. Thanks in advance to all who respond.

 ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'DD-MON-YYYY';

CREATE TABLE customers 
(CUSTOMER_ID, FIRST_NAME, LAST_NAME) AS
SELECT 1, 'Ann', 'Aaron' FROM DUAL UNION ALL
SELECT 2, 'Jane', 'Smith' FROM DUAL UNION ALL
SELECT 3, 'Bonnie', 'Winterbottom' FROM DUAL UNION ALL
SELECT 4, 'Sandy', 'Herring' FROM DUAL UNION ALL
SELECT 5, 'Roz', 'Doyle' FROM DUAL;


create table purchases(
  ORDER_ID NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
  customer_id   number, 
  PRODUCT_ID NUMBER, 
  QUANTITY NUMBER, 
  purchase_date timestamp
);

insert into purchases (customer_id, product_id, quantity, purchase_date)
    select  2 customer_id, 102 product_id, 2 quantity,
   TIMESTAMP '2024-04-03 00:00:00' + INTERVAL '18' HOUR + ((LEVEL-1) * INTERVAL '1 00:00:01' DAY TO SECOND)  * -1 +   ((LEVEL-1) * interval '0.007125' second) 
          as purchase_date
    from    dual
    connect by level <= 15 UNION all
    select  1, 101, 1,
         DATE '2024-03-08' + INTERVAL '14' HOUR + ((LEVEL-1) * INTERVAL '1 00:00:00' DAY TO SECOND) * -1
   from    dual
   connect by level <= 5 UNION ALL 
select  3, 103, 3,
         DATE '2024-02-08' + INTERVAL '15' HOUR + ((LEVEL-1) * INTERVAL '0 23:59:59' DAY TO SECOND) * -1
   from    dual
   connect by level <= 5
 UNION all
select 2, 102,1, date '2023-07-29' + level * interval '1' day from dual
          connect by level <= 12
union all
select 2, 103,1, date '2023-08-29' + level * interval '1' day from dual
          connect by level <= 15
union all
select 2, 104,1, date '2023-11-11' + level * interval '1' day from dual
          connect by level <= 9
union all
select 4, 103,(3*LEVEL), TIMESTAMP '2023-06-01 05:18:03' + numtodsinterval ( (LEVEL -1) * 1, 'day' ) + numtodsinterval ( LEVEL * 37, 'minute' ) +  numtodsinterval ( LEVEL * 3, 'second' ) FROM    dual
CONNECT BY  LEVEL <= 4 UNION ALL 
SELECT 3, 102, 4,TIMESTAMP '2022-12-22 21:44:35' + NUMTODSINTERVAL ( LEVEL * 3, 'DAY') FROM    dual
CONNECT BY  LEVEL <= 13 UNION ALL 
select 1, 104, (2 * LEVEL), date '2023-07-02' + level * interval '1 15:13' day to minute from dual
          connect by level <= 7
union all
select 1, 101,3, date '2023-03-29' + level * interval '2' day from dual
          connect by level <= 12
union all
select 2, 101,2, date '2023-01-15' + level * interval '8' hour from dual
          connect by level <= 15
union all
select 2, 102,2,date '2023-04-13' + level * interval '1 1' day to hour from dual
          connect by level <= 11
union all
select 3, 101,2, date '2023-02-01' + level * interval '1 05:03' day to minute from dual
          connect by level <= 10
union all
select 3, 101,1, date '2023-04-22' + level * interval '23' hour from dual
          connect by level <= 23
union all
select 3, 100,1,  date '2022-03-01' + level * interval '1 00:23:05' day to second from dual
          connect by level <= 15
union all
select 3, 101,1,  date '2022-03-01' + level * interval '1 00:23:05' day to second from dual
          connect by level <= 15
union all
select 4, 102,1, date '2023-01-01' + level * interval '5' hour from dual
          connect by level <= 60;

/* modify working query */

select m.customer_id,  
    c.first_name,
    c.last_name,
    m.first_date, 
    m.last_date, 
   trunc(m.last_date) - trunc(m.first_date) + 1 as consecutive_days
 FROM purchases pur match_recognize
(
    partition by customer_id
    order by purchase_date
    measures 
      first(purchase_date) as first_date, 
       last(purchase_date) as last_date 
     one row per match 
     pattern(start_date P{9,}) 
     define P as 
          purchase_date >= prev(trunc(purchase_date)) + interval '1' day 
    and purchase_date < prev(trunc(purchase_date)) + interval '2' day 
) m 
LEFT OUTER JOIN customers c ON c.customer_id = m.customer_id;

/* expected result */

CUSTOMER_ID FIRST_NAME LAST_NAME FIRST_DATE LAST_DATE CONSECUTIVE_DAYS

2 JANE SMITH 30-AUG-2023 13-SEP-2023 15
2 JANE SMITH 20-MAR-2024 03-APR-2024 15
3 BONNIE WINTERBOTTOM 22-APR-2023 14-MAY-2023 23
This post has been answered by Solomon Yakobson on May 6 2024
Jump to Answer
Comments
Post Details
Added on May 6 2024
11 comments
372 views