Agrupación de resultados SQL por intervalos de tiempo continuos (Oracle SQL)
-
06-07-2019 - |
Pregunta
Hola, tengo los siguientes datos en la tabla:
ID ----- startDate ---- endDate
5549 01-05-2008 4712-12-31
5567 2008-04-17 2008-04-30 1
5567 2008-05-01 2008-07-31 1
5567 2008-09-01 4712-12-31 2
5569 2008-05-01 2008-08-31
5569 01-09-2008 4712-12-31
5589 2008-04-18 2008-04-30
5589 2008-05-01 4712-12-31
5667 2008-05-01 4712-12-31
5828 2008-06-03 4712-12-31
5867 2008-06-03 4712-12-31
6167 2008-11-01 4712-12-31
6207 2008-07-01 4712-12-31
6228 2008-07-01 4712-12-31
6267 2008-07-14 4712-12-31
Estoy buscando la forma de agrupar los intervalos de tiempo continuos para que regrese cada ID:
ID, min (fecha de inicio), max (endDate),
tener algo como esto como resultado de la ID en negrita 5567
5567 2008-04-17 2008-07-31
5567 2008-09-01 4712-12-31
PL / SQL también es una opción aquí :)
Gracias,
Solución
Creo que esto hará lo que necesitas: (tenga en cuenta que probablemente se confundirá con rangos superpuestos; no sé si son posibles en su conjunto de datos)
select id, min(start_date) period_start, max(end_date) period_end
from
(
select
id, start_date, end_date,
max(contig) over (partition by id order by end_date) contiguous_group
from
(
select
id, start_date, end_date,
case
when lag(end_date) over (partition by id order by end_date) != start_date-1 or row_number() over (partition by id order by end_date)=1
then row_number() over (partition by id order by end_date) else null end contig
from t2
)
)
group by id, contiguous_group
order by id, period_start
/
Aquí están los datos de prueba que utilicé, basados ??en los suyos con un par de entradas adicionales:
create table t2 (id number, start_date date, end_date date);
insert into t2(id, start_date, end_date)values(5549, to_date('2008-05-01', 'yyyy-mm-dd'), to_date('4712-12-31', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(5567, to_date('2008-04-17', 'yyyy-mm-dd'), to_date('2008-04-30', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(5567, to_date('2008-05-01', 'yyyy-mm-dd'), to_date('2008-07-31', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(5567, to_date('2008-08-01', 'yyyy-mm-dd'), to_date('2008-08-14', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(5567, to_date('2009-09-01', 'yyyy-mm-dd'), to_date('4712-12-31', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(5567, to_date('2008-11-17', 'yyyy-mm-dd'), to_date('2008-12-13', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(5567, to_date('2008-12-14', 'yyyy-mm-dd'), to_date('2008-12-24', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(5569, to_date('2008-05-01', 'yyyy-mm-dd'), to_date('2008-08-31', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(5569, to_date('2008-09-01', 'yyyy-mm-dd'), to_date('4712-12-31', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(5589, to_date('2008-04-18', 'yyyy-mm-dd'), to_date('2008-04-30', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(5589, to_date('2008-05-01', 'yyyy-mm-dd'), to_date('4712-12-31', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(5667, to_date('2008-05-01', 'yyyy-mm-dd'), to_date('4712-12-31', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(5828, to_date('2008-06-03', 'yyyy-mm-dd'), to_date('4712-12-31', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(5867, to_date('2008-06-03', 'yyyy-mm-dd'), to_date('4712-12-31', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(6167, to_date('2008-11-01', 'yyyy-mm-dd'), to_date('4712-12-31', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(6207, to_date('2008-07-01', 'yyyy-mm-dd'), to_date('4712-12-31', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(6228, to_date('2008-07-01', 'yyyy-mm-dd'), to_date('4712-12-31', 'yyyy-mm-dd'));
insert into t2(id, start_date, end_date)values(6267, to_date('2008-07-14', 'yyyy-mm-dd'), to_date('4712-12-31', 'yyyy-mm-dd'));
commit;
Otros consejos
Podría hacer esto con funciones analíticas como esta:
with d as
( select id, start_date, end_date
, case when start_date = prev_end+1
then 'cont' else 'new' end start_status
, case when end_date = next_start-1
then 'cont' else 'new' end end_stat
from
(
select id, start_date, end_date
, lag(end_date) over (partition by id order by start_date) prev_end
, lead(start_date) over (partition by id order by start_date) next_start
from t1
order by id, start_date
)
)
select starts.id, starts.start_date, ends.end_date
from
( select id, start_date, row_number() over (order by id, start_date) rn
from d
where start_status='new'
) starts,
( select id, end_date, row_number() over (order by id, start_date) rn
from d
where end_status='new'
) ends
where starts.rn = ends.rn
Obtengo este resultado con sus datos:
ID START_DATE END_DATE
---------- ---------- ----------
5549 2008-05-01 4712-12-31
5567 2008-04-17 2008-07-31
5567 2008-09-01 4712-12-31
5569 2008-05-01 4712-12-31
5589 2008-04-18 4712-12-31
5667 2008-05-01 4712-12-31
5828 2008-06-03 4712-12-31
5867 2008-06-03 4712-12-31
6167 2008-11-01 4712-12-31
6207 2008-07-01 4712-12-31
6228 2008-07-01 4712-12-31
6267 2008-07-14 4712-12-31
12 rows selected.
Cómo funciona:
- La cláusula WITH genera una vista D de los datos donde a cada fila se le asigna un " estado de inicio " y un " estado final " ;, cada uno de los cuales es 'nuevo' o 'cont' para indicar si es continuo con la fila anterior / siguiente o no.
- Vistas en línea " comienza " y "termina" extraiga solo las filas que tienen un " nuevo " estado inicial / final respectivamente, con un número de fila para unirlos.
- La " consulta principal " luego selecciona entre estas 2 vistas y se une en la columna de número de fila.
deberá escribir una lógica de muestra de bloque PL / SQL como se muestra a continuación;
Create or Replace someproc
Declare
Cursore someCur AS
Select * from someTable
Order by ID,StartDate
IDVar as Varchar(10)
MinDate as DATE
MaxDate as DATE
Begin
Open someCur
Fetch ID,StartDate,EndDate into IDVar,MinDate,MaxDate
While SomeCur%NOTFOUND
LOOP
Fetch ID,StartDate,EndDate into TempID,TempStartDate,TempEndDate
if IDVar <> TempID then
-- output into your required structure values: IDVar,MinDate,MaxDate
IDVar = TempID
MinDate = TempStartDate
MaxDate = TempEndDate
Exit Loop
ELSE IF
MaxDate+1 >= TempStartDate THEN
MaxDate = TempEndDate
END IF
End LOOP
No estoy cerca de una instancia para probar, pero ¿lo has intentado?
SELECT
ID,
startDate,
endDate
FROM
myTable
WHERE
(ID, startDate) in
(SELECT
ID,
min(startDate)
FROM
myTable
GROUP BY
ID
)
or
(ID, endDate) in
(SELECT
ID,
max(endDate)
FROM
myTable
GROUP BY
ID
)
Esto debería proporcionarle todas las primeras startDate
s, y las últimas endDate
s para cada ID. Continuo o no.
WITH
laik_test AS -- sample data
(select 1001 id, date'2012-01-03' start_date, date'2012-06-29' end_date from dual union
select 1001 id, date'2012-03-03' start_date, date'2012-08-29' end_date from dual union
select 1002 id, date'2012-06-03' start_date, date'2012-11-29' end_date from dual union
select 1001 id, date'2012-09-03' start_date, date'2013-02-20' end_date from dual union
select 1001 id, date'2013-02-08' start_date, date'2013-04-29' end_date from dual union
select 1002 id, date'2012-11-03' start_date, date'2012-12-29' end_date from dual union
select 1002 id, date'2012-12-23' start_date, date'2013-09-29' end_date from dual union
select 1002 id, date'2013-08-03' start_date, date'2015-06-29' end_date from dual union
select 1001 id, date'2013-04-13' start_date, date'2013-09-29' end_date from dual union
select 1001 id, date'2013-07-03' start_date, date'2014-06-29' end_date from dual union
select 1003 id, date'2012-12-23' start_date, date'2013-09-29' end_date from dual union
select 1001 id, date'2013-07-03' start_date, date'2014-06-29' end_date from dual union
select 1003 id, date'2012-12-23' start_date, date'2013-09-29' end_date from dual union
select 1003 id, date'2013-09-30' start_date, date'2014-06-29' end_date from dual union
select 1003 id, date'2013-12-30' start_date, date'2014-03-11' end_date from dual union
select 1003 id, date'2014-06-29' start_date, date'2015-09-29' end_date from dual )
, matrica AS
( select id, start_date, end_date
, lead(start_date) over (partition by id order by start_date, end_date) start_date_next
, lag(end_date) over (partition by id order by start_date, end_date) end_date_prev
from laik_test m
where not exists (select *
from laik_test n
where m.id = n.id
and m.start_date > n.start_date and m.end_date < n.end_date))
, matrica2 AS
(select id, end_date
from matrica m
where start_date_next is null OR start_date_next > end_date + 1)
, matrica3 AS
(select id, start_date
from matrica m
where end_date_prev is null OR end_date_prev < start_date - 1)
, matrica4 AS
(select m2.id, m3.start_date, m2.end_date
from matrica2 m2, matrica3 m3
where m2.id=m3.id and m3.start_date < m2.end_date)
select id, start_date, end_date
from matrica4 m
where not exists (select * from matrica4 n
where m.id = n.id
and ( (n.start_date = m.start_date and m.end_date > n.end_date)
OR (n.end_date = m.end_date and m.start_date < n.start_date)
));
Resultados es:
ID | start_date | end_date
1001 | 2012-01-03 | 2012-08-29
1001 | 2012-09-03 | 2014-06-29
1002 | 2012-06-03 | 2015-06-29
1003 | 2012-12-23 | 2015-09-29
He estado tratando de hacer para la superposición de rangos, pero tengo algunas dificultades con estos datos:
INSERT INTO zzz_scrap_dates (id,effdate,termdate)
SELECT id,effdate,termdate from (
SELECT '1'id ,To_Date('2000-01-01','YYYY-MM-DD')effdate,To_Date('2020-01-31','YYYY-MM-DD')termdate FROM dual
UNION
SELECT '1'id ,To_Date('2000-01-01','YYYY-MM-DD')effdate,To_Date('2010-01-31','YYYY-MM-DD')termdate FROM dual
UNION
SELECT '1'id ,To_Date('2005-01-01','YYYY-MM-DD')effdate,To_Date('2020-01-31','YYYY-MM-DD')termdate FROM dual
UNION
SELECT '1'id ,To_Date('2000-01-01','YYYY-MM-DD')effdate,To_Date('2020-01-31','YYYY-MM-DD')termdate FROM dual
UNION
SELECT '1'id ,To_Date('1999-01-01','YYYY-MM-DD')effdate,To_Date('2020-01-31','YYYY-MM-DD')termdate FROM dual
union
SELECT '2'id ,To_Date('2000-01-01','YYYY-MM-DD')effdate,To_Date('2020-01-31','YYYY-MM-DD')termdate FROM dual
UNION
SELECT '2'id ,To_Date('2000-01-01','YYYY-MM-DD')effdate,To_Date('2010-01-31','YYYY-MM-DD')termdate FROM dual
UNION
SELECT '2'id ,To_Date('2005-01-01','YYYY-MM-DD')effdate,To_Date('2020-01-31','YYYY-MM-DD')termdate FROM dual
UNION
SELECT '2'id ,To_Date('2000-01-01','YYYY-MM-DD')effdate,To_Date('2020-01-31','YYYY-MM-DD')termdate FROM dual
UNION
SELECT '2'id ,To_Date('1999-01-01','YYYY-MM-DD')effdate,To_Date('2020-01-31','YYYY-MM-DD')termdate FROM dual
union
SELECT '3'id ,To_Date('2000-01-01','YYYY-MM-DD')effdate,To_Date('2020-01-31','YYYY-MM-DD')termdate FROM dual
UNION
SELECT '3'id ,To_Date('1998-01-01','YYYY-MM-DD')effdate,To_Date('1999-01-31','YYYY-MM-DD')termdate FROM dual
UNION
SELECT '3'id ,To_Date('1005-01-01','YYYY-MM-DD')effdate,To_Date('1197-01-31','YYYY-MM-DD')termdate FROM dual
UNION
SELECT '3'id ,To_Date('2000-01-01','YYYY-MM-DD')effdate,To_Date('2020-01-31','YYYY-MM-DD')termdate FROM dual
UNION
SELECT '3'id ,To_Date('1197-01-01','YYYY-MM-DD')effdate,To_Date('2020-01-31','YYYY-MM-DD')termdate FROM dual