2010-11-25 17 views
5

Tengo una tabla que contiene los tiempos de inicio (utilizando el número en el ejemplo para mantenerlo simple) y la duración de los eventos.Agrupar filas teniendo en cuenta la "diferencia" entre las filas

Me gustaría identificar los "bloques" y su hora de inicio y de finalización.
Cuando la diferencia entre la hora de finalización (hora de inicio + duración) de la fila anterior (ordenada por hora de inicio) y la hora de inicio de la fila actual es >=5, debe comenzar un nuevo "bloque".

Esta es mi prueba de-datos, incluyendo un intento de explicación gráfica en los comentarios:

WITH test_data AS (
    SELECT 0 s, 2 dur FROM dual UNION ALL --# ■■ 
    SELECT 2 , 2  FROM dual UNION ALL --# ■■ 
    SELECT 10 , 1  FROM dual UNION ALL --#   ■ 
    SELECT 13 , 4  FROM dual UNION ALL --#    ■■■■ 
    SELECT 15 , 4  FROM dual    --#    ■■■■ 
) 
--# Should return 
--# 0 .. 4        --# ■■■■ 
--# 10 .. 19        --#   ■■■■■■■■■ 

El primer bloque comienza a 0 y termina en 4. Dado que la diferencia a la siguiente fila es >=5, inicie otro bloque al 10 que termina en 19.


Puedo identificar la primera fila de un bloque, usando LAG, pero aún no han encontrado la manera de proceder.

Y podría resolver el problema en un PL/SQL-loop, pero estoy tratando de evitar eso por cuestiones de rendimiento.


¿Alguna sugerencia para formular esta consulta?

Gracias de antemano, Pedro

+1

+1 para los gráficos bastante fresco – thomaspaulb

Respuesta

3

que utilizan subconsultas con la analítica para identificar y rangos contiguos del grupo:

SQL> WITH test_data AS (
    2 SELECT 0 s, 2 dur FROM dual UNION ALL --# ■■ 
    3 SELECT 2 , 2  FROM dual UNION ALL --# ■■ 
    4 SELECT 10 , 1  FROM dual UNION ALL --#   ■ 
    5 SELECT 13 , 4  FROM dual UNION ALL --#    ■■■■ 
    6 SELECT 15 , 4  FROM dual    --#    ■■■■ 
    7 ) 
    8 SELECT MIN(s) "begin", MAX(s + dur) "end" 
    9 FROM (SELECT s, dur, SUM(gap) over(ORDER BY s) my_group 
10    FROM (SELECT s, dur, 
11       CASE 
12        WHEN lag(s + dur) over(ORDER BY s) >= s - 5 THEN 
13        0 
14        ELSE 
15        1 
16       END gap 
17      FROM test_data 
18      ORDER BY s)) 
19 GROUP BY my_group; 

    begin  end 
---------- ---------- 
     0   4 
     10   19 
+0

Gracias Vincent! Eso es exactamente lo que necesitaba, me faltaba la parte 'SUM (gap) OVER ...'. Nota secundaria: creo que el 'ORDER BY s' podría eliminarse de la sub-selección interna, y moverse a la consulta principal si es necesario. –

1

En MS-SQL que usaría ROW_NUMBER() OVER(ORDER BY starttime) AS Rank para clasificar las filas en tiempo de inicio.

Luego, escribiría una consulta para unir cada línea a la línea con Rango anterior y establecer una bandera si la diferencia es mayor que cinco o NULL (primera fila).

Entonces, me seleccionar todas las filas que tienen esta bandera que son empezar a filas, y para este subconjunto repetir el proceso de numeración de filas y unirse a la siguiente fila para obtener el tiempo se extiende por:

blockstarttime1 nextstarttime1 (=starttime2) 
blockstarttime2 nextstarttime2 (=starttime3) 
blockstarttime3 NULL 

Por último, este conjunto de datos se puede unir a los datos originales con un WHERE starttime BETWEEN blockstarttime and nextstarttime para particionar los resultados.

Hasta que traducir esto a Oracle ...

+0

Gracias! Esto debería funcionar, pero implicaría otra consulta a los datos originales, que evita la solución provista por Vincent Malgrat y MikeyByCrikey. –

1

Hay un fantástico libro de Richard Snodgrass que puede ayudar: Developing Time-Oriented Database Applications in SQL (gratis para descargar) que he encontrado muy valiosa cuando se trata de tiempo en bases de datos.

Eche un vistazo al Richards page para obtener enlaces a algunas correcciones de libros y al CD-ROM asociado en formato zip.

+0

Gracias por el enlace, voy a leer esas 528 páginas más tarde :) ¿Comprobó si ya está en la [Lista de libros de programación disponibles gratuitamente] (http://stackoverflow.com/questions/194812/list- de-libre-disponible-programación-libros)? –

+0

@ Peter: Solo eché un vistazo, el libro aparece en "SQL (Implementación independiente)". – Tony

2

El código se vuelve un poco complicado con una serie de subconsultas, etc. Pueden haber instancias de datos en las que esto no funciona, pero no puedo pensar en ninguna de las más extravagantes.

¡Trabajar con datos temporales siempre es un problema!

WITH test_data AS (
    SELECT 0 s, 2 dur FROM dual UNION ALL --# ■■ 
    SELECT 2 , 2  FROM dual UNION ALL --# ■■ 
    SELECT 10 , 1  FROM dual UNION ALL --#   ■ 
    SELECT 13 , 4  FROM dual UNION ALL --#    ■■■■ 
    SELECT 15 , 4  FROM dual    --#    ■■■■ 
) 
select 
-- Group on each block 
    min(start_time) as s, 
    max(end_time) - min(start_time) as dur 
from (
    select 
    start_time, 
    duration, 
    end_time, 
-- number the blocks sequentially 
    sum(is_block_start) over (order by start_time) as block_num 
    from (
    select 
     start_time, 
     duration, 
     end_time, 
-- Mark the start of each block 
     case 
     when nvl2(prev_end_time, start_time - prev_end_time,5) >= 5 
     then 1 else 0 end as is_block_start 
    from (
     select 
     s as start_time, 
     dur as duration, 
     s+dur as end_time, 
     lag(s+dur) over (order by s) prev_end_time 
     from test_data 
    ) 
) 
) 
group by block_num 
+0

¡Gracias! Me faltaba la parte 'sum (is_block_start) over'. Aceptando la respuesta de Vincents, que es más o menos la misma consulta que proporcionó, pero me resulta más fácil de leer. –

Cuestiones relacionadas