To jest problem luk i wysp. Można do tego podejść na różne sposoby; używa lead
i lag
funkcje analityczne:
select distinct product,
case when start_date is null then lag(start_date)
over (partition by product order by rn) else start_date end as start_date,
case when end_date is null then lead(end_date)
over (partition by product order by rn) else end_date end as end_date
from (
select product, start_date, end_date, rn
from (
select t.product,
case when lag(end_date)
over (partition by product order by start_date) is null
or lag(end_date)
over (partition by product order by start_date) != start_date - 1
then start_date end as start_date,
case when lead(start_date)
over (partition by product order by start_date) is null
or lead(start_date)
over (partition by product order by start_date) != end_date + 1
then end_date end as end_date,
row_number() over (partition by product order by start_date) as rn
from t
)
where start_date is not null or end_date is not null
)
order by start_date, product;
PRODUCT START_DATE END_DATE
------- ---------- ---------
A 01-JUL-13 30-SEP-13
B 01-OCT-13 30-NOV-13
A 01-DEC-13 31-MAR-14
Najbardziej wewnętrzne zapytanie analizuje poprzedni i następny rekord produktu i zachowuje czas rozpoczęcia i/lub zakończenia tylko wtedy, gdy rekordy nie są ciągłe:
select t.product,
case when lag(end_date)
over (partition by product order by start_date) is null
or lag(end_date)
over (partition by product order by start_date) != start_date - 1
then start_date end as start_date,
case when lead(start_date)
over (partition by product order by start_date) is null
or lead(start_date)
over (partition by product order by start_date) != end_date + 1
then end_date end as end_date
from t;
PRODUCT START_DATE END_DATE
------- ---------- ---------
A 01-JUL-13
A
A 30-SEP-13
A 01-DEC-13
A
A
A 31-MAR-14
B 01-OCT-13
B 30-NOV-13
Następny poziom wyboru usuwa te, które są w połowie okresu, w których obie daty zostały wymazane przez wewnętrzne zapytanie, co daje:
PRODUCT START_DATE END_DATE
------- ---------- ---------
A 01-JUL-13
A 30-SEP-13
A 01-DEC-13
A 31-MAR-14
B 01-OCT-13
B 30-NOV-13
Zewnętrzne zapytanie następnie zwija te sąsiednie pary; Skorzystałem z łatwej drogi tworzenia duplikatów, a następnie eliminowania ich za pomocą distinct
, ale możesz to zrobić na inne sposoby, na przykład wstawiając obie wartości do jednej z par wierszy i pozostawiając obie wartości w drugiej wartości null, a następnie eliminując te z inną warstwą zaznaczania, ale myślę, że odróżnienie jest tutaj OK.
Jeśli Twój rzeczywisty przypadek użycia ma godziny, a nie tylko daty, musisz dostosować porównanie w zapytaniu wewnętrznym; zamiast +/- 1, być może w odstępie 1 sekundy lub 1/86400, jeśli wolisz, ale zależy to od precyzji Twoich wartości.