Suma Intervalo de fechas dentro de la misma columna

10

¿Cómo sumas mejor las diferencias de un rango de fechas en la misma columna entre filas intercaladas? Tengo una columna de fecha y hora y quiero calcular la diferencia entre las filas. Quiero la diferencia en segundos. Esta pregunta no trata sobre cómo obtener una diferencia entre 2 marcas de tiempo, sino que se centra más en cómo calcular de manera más eficiente entre filas en la misma tabla. En mi caso, cada fila tiene un tipo de evento datetime que enlaza 2 filas lógicamente.

Detalles relacionados con cómo agrupar los tipos de evento de inicio y fin. (Pregunta de Andriy M) Comienza y termina "debería" ser consecutivo. Si un inicio no tiene un final posterior, debe quedar fuera de la suma. Pasar al siguiente inicio para ver si tiene un final. Solo se deben agregar pares consecutivos de inicio - final a la suma de los segundos totales.

Trabajando en postgresql 9.x ...

Datos de ejemplo en la tabla;

eventtype, eventdate
START, 2015-01-01 14:00
END, 2015-01-01 14:25
START, 2015-01-01 14:30
END, 2015-01-01 14:43
START, 2015-01-01 14:45
END, 2015-01-01 14:49
START, 2015-01-01 14:52
END, 2015-01-01 14:55

Tenga en cuenta que todas las fechas de inicio y finalización serán secuenciales.

Aquí está mi primer intento. Parece estar funcionando.

SELECT 
-- starts.*
SUM(EXTRACT(EPOCH FROM (eventdate_next - eventdate))) AS duration_seconds
FROM
( 
    WITH x AS (
        SELECT *, dense_rank() OVER (ORDER BY eventdate) AS rnk
        FROM   table
        AND eventdate > '2015-01-01 00:00:00.00'
        AND eventdate < '2016-01-01 23:59:59.59' 
        )
    SELECT x.eventdate, x.eventtype, y.eventdate AS eventdate_next,  y.eventtype AS eventtype_next
    FROM   x
    LEFT   JOIN (SELECT DISTINCT eventdate, eventtype, rnk FROM x) y ON y.rnk = (x.rnk + 1)
    ORDER  BY x.eventdate
) starts
WHERE
eventtype = 'START'   
GROUP BY eventtype 

Mi primer intento se basa en un gran ejemplo de stackoverflow Postgres 9.1: obtener el siguiente valor

Nota; Puede comentar GROUP BY y SUM y descomentar los inicios. * Para obtener un registro de cada duración individual que ingresa en la suma.

C Smith
fuente

Respuestas:

10

Puede usar la LEADfunción analítica para obtener la siguiente fila eventtypey eventdatejunto con los datos de la fila actual:

SELECT
  eventtype,
  eventdate,
  LEAD(eventtype) OVER (ORDER BY eventdate) AS nexttype,
  LEAD(eventdate) OVER (ORDER BY eventdate) AS nextdate
FROM
  atable
WHERE
      eventdate >= '2015-01-01 00:00:00.00'
  AND eventdate <  '2016-01-01 23:59:59.59'

Usando la consulta anterior como una tabla derivada, puede filtrar la salida más adelante eventtype = 'START' AND nexttype = 'END'y obtener la diferencia total:

SELECT
  SUM(EXTRACT(EPOCH FROM (nextdate - eventdate))) AS duration_seconds
FROM
  (
    SELECT
      eventtype,
      eventdate,
      LEAD(eventtype) OVER (ORDER BY eventdate) AS nexttype,
      LEAD(eventdate) OVER (ORDER BY eventdate) AS nextdate
    FROM
      atable
    WHERE
          eventdate >= '2015-01-01 00:00:00.00'
      AND eventdate <  '2016-01-01 23:59:59.59'
  ) AS s
WHERE
      eventtype = 'START'
  AND nexttype  = 'END'
;

Como una ligera variación, puede implementar la subconsulta como CTE:

WITH cte AS
  (
    SELECT
      eventtype,
      eventdate,
      LEAD(eventtype) OVER (ORDER BY eventdate) AS nexttype,
      LEAD(eventdate) OVER (ORDER BY eventdate) AS nextdate
    FROM
      atable
    WHERE
          eventdate >= '2015-01-01 00:00:00.00'
      AND eventdate <  '2016-01-01 23:59:59.59'
  )
SELECT
  SUM(EXTRACT(EPOCH FROM (nextdate - eventdate))) AS duration_seconds
FROM
  cte
WHERE
      eventtype = 'START'
  AND nexttype  = 'END'
;

Esta reescritura puede tener implicaciones para el rendimiento, porque a diferencia de una tabla derivada, un CTE se materializa en PostgreSQL. Las pruebas deben revelar si hay una diferencia y, de ser así, qué opción es mejor para usted.

Andriy M
fuente
Andriy, gracias! Probaré la versión CTE y veré cómo ayuda.
C Smith