Soma intervalo de datas na mesma coluna

10

Como você soma as diferenças de um intervalo de datas na mesma coluna entre as linhas intercaladas? Eu tenho uma coluna de data e hora e quero calcular a diferença entre linhas. Eu quero a diferença em segundos. Esta pergunta não é sobre como obter uma diferença entre dois registros de data e hora, mas é mais focada em como calcular com mais eficiência entre linhas na mesma tabela. No meu caso, cada linha tem um tipo de evento datetime que vincula duas linhas logicamente.

Detalhes Relacionados a como agrupar os tipos de evento de início e fim. (Pergunta de Andriy M) Começos e fins "devem" ser consecutivos. Se um início não tiver um fim subsequente, ele deve ser deixado de fora da soma. Mover para o próximo Iniciar para ver se ele tem um fim. Somente pares consecutivos de início e fim devem ser adicionados à soma do total de segundos.

Trabalhando no postgresql 9.x ...

Dados de exemplo na tabela;

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

Observe que todas as datas de início e término serão seqüenciais.

Aqui está minha primeira tentativa. 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 

Minha primeira tentativa é baseada em um ótimo exemplo do stackoverflow Postgres 9.1 - Obtendo o próximo valor

Nota; Você pode comentar o GROUP BY e o SUM e cancelar o comentário das partidas. * Para obter um registro para cada duração individual que entra na soma.

C Smith
fonte

Respostas:

10

Você pode usar a LEADfunção analítica para obter as próximas linhas eventtypee eventdateao lado dos dados da linha atual:

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 a consulta acima como uma tabela derivada, você pode filtrar a saída ainda mais eventtype = 'START' AND nexttype = 'END'e obter a diferença 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 uma pequena variação, você pode implementar a subconsulta como um 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'
;

Essa reescrita pode ter implicações no desempenho, porque, diferentemente de uma tabela derivada, uma CTE é materializada no PostgreSQL. Os testes devem revelar se há uma diferença e, se houver, qual opção é melhor para você.

Andriy M
fonte
Andriy, obrigado! Vou tentar a versão CTE e ver como isso ajuda.
C Smith