Oracle
 sql >> Base de Dados >  >> RDS >> Oracle

Unindo intervalos de validade de data consecutivos


Este é um problema de lacunas e ilhas. Existem várias maneiras de abordá-lo; isso usa lead e lag funções analíticas:
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 

SQL Fiddle

A consulta mais interna examina os registros anteriores e seguintes do produto e só retém a hora de início e/ou término se os registros não forem contíguos:
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 

O próximo nível de seleção remove aqueles que estão no meio do período, onde ambas as datas foram apagadas pela consulta interna, que fornece:
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 

A consulta externa então recolhe esses pares adjacentes; Eu usei o caminho fácil de criar duplicatas e depois eliminá-las com distinct , mas você pode fazer isso de outras maneiras, como colocar os dois valores em um dos pares de linhas e deixar os dois valores no outro null e, em seguida, eliminar aqueles com outra camada de seleção, mas acho que distinto está OK aqui.

Se o seu caso de uso do mundo real tiver horas, não apenas datas, você precisará ajustar a comparação na consulta interna; em vez de +/- 1, um intervalo de 1 segundo talvez, ou 1/86400 se preferir, mas depende da precisão de seus valores.