Search code examples
sqloracleoracle11g

Display transactions data with consecutive days


I have this Transactions table in Oracle 11G. Please, don't judge my asking question.

Table has columns:

id (guid type)
sender_name
transaction_date
amount
sender_address
sender_phone
receiver_name

Format of transaction_date column is like 2014-11-30 04:26:29.000000.

What I want is: to display all data from Transactions table where those sender_name who sent more than 1000 amount (sum(amount)) on 5 consecutive days in given transaction_date between 01.01.2023 to 01.10.2023.

Even if, for example sender_name sent money on April month 5 consecutive days and on June also have sent 5 consecutive days display all.

Could someone tell how to do this, please?

Sample data:

CREATE TABLE transactions (id, sender_name, transaction_date, amount) AS
SELECT '1111', 'Alice', TIMESTAMP '2014-11-26 01:26:29', 1001 FROM DUAL UNION ALL
SELECT '2222', 'Alice', TIMESTAMP '2014-11-27 02:26:29', 1001 FROM DUAL UNION ALL
SELECT '3333', 'Alice', TIMESTAMP '2014-11-28 03:26:29', 1001 FROM DUAL UNION ALL
SELECT '4444', 'Alice', TIMESTAMP '2014-11-29 04:26:29', 1001 FROM DUAL UNION ALL
SELECT '5555', 'Alice', TIMESTAMP '2014-11-30 05:26:29', 1001 FROM DUAL UNION ALL

SELECT '6666', 'Betty', TIMESTAMP '2014-11-30 05:26:29', 1001 FROM DUAL UNION ALL
SELECT '7777', 'Betty', TIMESTAMP '2014-11-30 05:26:29', 1001 FROM DUAL UNION ALL
  
SELECT '9999', 'Carol', TIMESTAMP '2014-11-26 01:26:29', 1001 FROM DUAL UNION ALL
SELECT '8888', 'Carol', TIMESTAMP '2014-11-27 02:26:29', 1001 FROM DUAL UNION ALL
SELECT '1234', 'Carol', TIMESTAMP '2014-11-28 03:26:29',  500 FROM DUAL UNION ALL
SELECT '4567', 'Carol', TIMESTAMP '2014-11-28 08:26:29',  501 FROM DUAL UNION ALL
SELECT '2345', 'Carol', TIMESTAMP '2014-11-29 04:26:29', 1001 FROM DUAL UNION ALL
SELECT '3456', 'Carol', TIMESTAMP '2014-11-30 05:26:29', 1001 FROM DUAL UNION ALL
SELECT '5678', 'Carol', TIMESTAMP '2014-12-01 05:26:29', 1001 FROM DUAL UNION ALL
  
SELECT '9999', 'Debra', TIMESTAMP '2014-11-26 01:26:29', 1001 FROM DUAL UNION ALL
SELECT '8888', 'Debra', TIMESTAMP '2014-11-27 02:26:29', 1001 FROM DUAL UNION ALL
SELECT '1234', 'Debra', TIMESTAMP '2014-11-28 03:26:29', 1001 FROM DUAL UNION ALL
SELECT '4567', 'Debra', TIMESTAMP '2014-11-28 08:26:29', 1001 FROM DUAL UNION ALL
SELECT '2345', 'Debra', TIMESTAMP '2014-11-30 04:26:29', 1001 FROM DUAL UNION ALL
SELECT '3456', 'Debra', TIMESTAMP '2014-11-30 05:26:29', 1001 FROM DUAL UNION ALL
SELECT '5678', 'Debra', TIMESTAMP '2014-12-01 05:26:29', 1001 FROM DUAL;

The result I want all datas of Alice-s rows. Because of total amount each they sent more than 10000 on 5 consecutive days. If I change 5 consecutive days to more than 5, than display Carol-s rows.


Solution

  • Since you can have multiple transactions occurring on a single day then you can use the DENSE_RANK analytic function over the days for each sender_name to find the groups of consecutive days. Then for each group you can find the start and end days and filter to only those rows where there are 5 or more days:

    SELECT id,
           sender_name,
           transaction_date,
           amount
    FROM   (
      SELECT id,
             sender_name,
             transaction_date,
             amount,
             MAX(TRUNC(transaction_date)) OVER (PARTITION BY sender_name, grp)
             - MIN(TRUNC(transaction_date)) OVER (PARTITION BY sender_name, grp)
             + 1
               AS num_days
      FROM   (
        SELECT id,
               sender_name,
               transaction_date,
               amount,
               TRUNC(transaction_date)
               - DENSE_RANK() OVER (
                   PARTITION BY sender_name ORDER BY TRUNC(transaction_date)
                 ) AS grp
        FROM   (
          SELECT id,
                 sender_name,
                 transaction_date,
                 amount,
                 SUM(amount) OVER (
                   PARTITION BY sender_name, TRUNC(transaction_date)
                 ) AS daily_amount
          FROM   transactions
        )
        WHERE  daily_amount > 1000
      )
    )
    WHERE  num_days >= 5
    ORDER BY sender_name, transaction_date;
    

    Which, for the sample data:

    CREATE TABLE transactions (id, sender_name, transaction_date, amount) AS
    SELECT '1111', 'Alice', TIMESTAMP '2014-11-26 01:26:29', 1001 FROM DUAL UNION ALL
    SELECT '2222', 'Alice', TIMESTAMP '2014-11-27 02:26:29', 1001 FROM DUAL UNION ALL
    SELECT '3333', 'Alice', TIMESTAMP '2014-11-28 03:26:29', 1001 FROM DUAL UNION ALL
    SELECT '4444', 'Alice', TIMESTAMP '2014-11-29 04:26:29', 1001 FROM DUAL UNION ALL
    SELECT '5555', 'Alice', TIMESTAMP '2014-11-30 05:26:29', 1001 FROM DUAL UNION ALL
    
    SELECT '6666', 'Betty', TIMESTAMP '2014-11-30 05:26:29', 1001 FROM DUAL UNION ALL
    SELECT '7777', 'Betty', TIMESTAMP '2014-11-30 05:26:29', 1001 FROM DUAL UNION ALL
      
    SELECT '9999', 'Carol', TIMESTAMP '2014-11-26 01:26:29', 1001 FROM DUAL UNION ALL
    SELECT '8888', 'Carol', TIMESTAMP '2014-11-27 02:26:29', 1001 FROM DUAL UNION ALL
    SELECT '1234', 'Carol', TIMESTAMP '2014-11-28 03:26:29',  500 FROM DUAL UNION ALL
    SELECT '4567', 'Carol', TIMESTAMP '2014-11-28 08:26:29',  501 FROM DUAL UNION ALL
    SELECT '2345', 'Carol', TIMESTAMP '2014-11-29 04:26:29', 1001 FROM DUAL UNION ALL
    SELECT '3456', 'Carol', TIMESTAMP '2014-11-30 05:26:29', 1001 FROM DUAL UNION ALL
    SELECT '5678', 'Carol', TIMESTAMP '2014-12-01 05:26:29', 1001 FROM DUAL UNION ALL
      
    SELECT '9999', 'Debra', TIMESTAMP '2014-11-26 01:26:29', 1001 FROM DUAL UNION ALL
    SELECT '8888', 'Debra', TIMESTAMP '2014-11-27 02:26:29', 1001 FROM DUAL UNION ALL
    SELECT '1234', 'Debra', TIMESTAMP '2014-11-28 03:26:29', 1001 FROM DUAL UNION ALL
    SELECT '4567', 'Debra', TIMESTAMP '2014-11-28 08:26:29', 1001 FROM DUAL UNION ALL
    SELECT '2345', 'Debra', TIMESTAMP '2014-11-30 04:26:29', 1001 FROM DUAL UNION ALL
    SELECT '3456', 'Debra', TIMESTAMP '2014-11-30 05:26:29', 1001 FROM DUAL UNION ALL
    SELECT '5678', 'Debra', TIMESTAMP '2014-12-01 05:26:29', 1001 FROM DUAL;
    

    Outputs:

    ID SENDER_NAME TRANSACTION_DATE AMOUNT
    1111 Alice 2014-11-26 01:26:29.000000000 1001
    2222 Alice 2014-11-27 02:26:29.000000000 1001
    3333 Alice 2014-11-28 03:26:29.000000000 1001
    4444 Alice 2014-11-29 04:26:29.000000000 1001
    5555 Alice 2014-11-30 05:26:29.000000000 1001
    9999 Carol 2014-11-26 01:26:29.000000000 1001
    8888 Carol 2014-11-27 02:26:29.000000000 1001
    1234 Carol 2014-11-28 03:26:29.000000000 500
    4567 Carol 2014-11-28 08:26:29.000000000 501
    2345 Carol 2014-11-29 04:26:29.000000000 1001
    3456 Carol 2014-11-30 05:26:29.000000000 1001
    5678 Carol 2014-12-01 05:26:29.000000000 1001

    Oracle 11g fiddle

    If you were using Oracle 12 or later then you can greatly simplify the solution by using MATCH_RECOGNIZE:

    SELECT *
    FROM   (
      SELECT id,
             sender_name,
             transaction_date,
             amount
      FROM   (
        SELECT id,
               sender_name,
               transaction_date,
               amount,
               SUM(amount) OVER (
                 PARTITION BY sender_name, TRUNC(transaction_date)
               ) AS daily_amount
        FROM   transactions
      )
      WHERE  daily_amount > 1000
    )
    MATCH_RECOGNIZE (
      PARTITION BY sender_name
      ORDER BY transaction_date
      ALL ROWS PER MATCH
      PATTERN (consecutive_days+ last_day)
      DEFINE
        consecutive_days AS NEXT(transaction_date) < TRUNC(transaction_date) + 2,
        last_day AS FIRST(TRUNC(transaction_date)) + 4 <= transaction_date
    )
    

    Oracle 18 fiddle