Denis Ka
Denis Ka

Reputation: 137

Create time intervals based on values in one column / SQL Oracle

I need to create query that will return time intervals from table, that has attributes for (almost) every day.
The original table looks like the following:

Person | Date       | Date_Type
-------|------------|----------
Sam    | 01.06.2020 |  Vacation
Sam    | 02.06.2020 |  Vacation
Sam    | 03.06.2020 |  Work
Sam    | 04.06.2020 |  Work
Sam    | 05.06.2020 |  Work
Frodo  | 01.06.2020 |  Work
Frodo  | 02.06.2020 |  Work
.....

And the desired should look like:

Person | Date_Interval         | Date_Type
-------|-----------------------|----------
Sam    | 01.06.2020-02.06.2020 |  Vacation
Sam    | 03.06.2020-05.06.2020 |  Work
Frodo  | 01.06.2020-02.06.2020 |  Work
.....

Will be grateful for any idea :)

Upvotes: 0

Views: 1000

Answers (3)

MT0
MT0

Reputation: 167981

One of the simplest methods is to use MATCH_RECOGNIZE to perform a row-by-row comparison and aggregation:

SELECT *
FROM   table_name
MATCH_RECOGNIZE (
  PARTITION BY Person
  ORDER     BY "DATE"
  MEASURES
    FIRST( "DATE" )    AS start_date,
    LAST( "DATE")      AS end_date,
    FIRST( Date_Type ) AS date_type
  ONE ROW PER MATCH
  PATTERN ( successive_dates+ )
  DEFINE
    SUCCESSIVE_DATES AS (
          FIRST( Date_Type ) = NEXT( Date_Type )
      AND MAX( "DATE" ) + INTERVAL '1' DAY = NEXT( "DATE")
    )
);

Which, for the sample data:

CREATE TABLE table_name ( Person, "DATE", Date_Type ) AS
SELECT 'Sam',   DATE '2020-06-01', 'Vacation' FROM DUAL UNION ALL
SELECT 'Sam',   DATE '2020-06-02', 'Vacation' FROM DUAL UNION ALL
SELECT 'Sam',   DATE '2020-06-03', 'Work' FROM DUAL UNION ALL
SELECT 'Sam',   DATE '2020-06-04', 'Work' FROM DUAL UNION ALL
SELECT 'Sam',   DATE '2020-06-05', 'Work' FROM DUAL UNION ALL
SELECT 'Frodo', DATE '2020-06-01', 'Work' FROM DUAL UNION ALL
SELECT 'Frodo', DATE '2020-06-02', 'Work' FROM DUAL;

Outputs:

PERSON | START_DATE          | END_DATE            | DATE_TYPE
:----- | :------------------ | :------------------ | :--------
Frodo  | 2020-06-01 00:00:00 | 2020-06-01 00:00:00 | Work     
Sam    | 2020-06-01 00:00:00 | 2020-06-01 00:00:00 | Vacation 
Sam    | 2020-06-03 00:00:00 | 2020-06-04 00:00:00 | Work     

db<>fiddle here

Upvotes: 1

Gordon Linoff
Gordon Linoff

Reputation: 1269823

This is a type of gaps-and-islands problem.

To get adjacent days with the same date_type, you can subtract a sequence. It will be constant for adjacent days. Then you can aggregate:

select person, date_type, min(date), max(date)
from (select t.*,
             row_number() over (partition by person, date_type
                                             order by date) as seqnum
      from t
     ) t
group by person, date_type, (date - seqnum);

Upvotes: 1

GMB
GMB

Reputation: 222482

This reads like a gaps-and-island problem. Here is one approach:

select person, min(date) startdate, max(date) enddate, date_type
from (
    select t.*,
        row_number() over(partition by person order by date) rn1,
        row_number() over(partition by person, date_type order by date) rn2
    from mytable t
) t
group by person, date_type, rn1 - rn2

This also works if not all dates are contiguous (since you stated that you have almost all dates, I understood you don't have them all).

Upvotes: 1

Related Questions