PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Melhor maneira de contar registros por intervalos de tempo arbitrários em Rails+Postgres


Felizmente, você está usando o PostgreSQL. A função de janela generate_series() é seu amigo.

Caso de teste


Dada a seguinte tabela de teste (que você deveria ter fornecido):
CREATE TABLE event(event_id serial, ts timestamp);
INSERT INTO event (ts)
SELECT generate_series(timestamp '2018-05-01'
                     , timestamp '2018-05-08'
                     , interval '7 min') + random() * interval '7 min';

Um evento a cada 7 minutos (mais 0 a 7 minutos, aleatoriamente).

Solução básica


Essa consulta conta eventos para qualquer intervalo de tempo arbitrário. 17 minutos no exemplo:
WITH grid AS (
   SELECT start_time
        , lead(start_time, 1, 'infinity') OVER (ORDER BY start_time) AS end_time
   FROM  (
      SELECT generate_series(min(ts), max(ts), interval '17 min') AS start_time
      FROM   event
      ) sub
   )
SELECT start_time, count(e.ts) AS events
FROM   grid       g
LEFT   JOIN event e ON e.ts >= g.start_time
                   AND e.ts <  g.end_time
GROUP  BY start_time
ORDER  BY start_time;

  • A consulta recupera ts mínimo e máximo da tabela base para cobrir todo o intervalo de tempo. Você pode usar um intervalo de tempo arbitrário.

  • Forneça qualquer intervalo de tempo como necessário.

  • Produz uma linha para cada intervalo de tempo. Se nenhum evento aconteceu durante esse intervalo, a contagem é 0 .

  • Certifique-se de lidar com limite superior e inferior corretamente:
    • Resultados inesperados da consulta SQL com BETWEEN timestamps

  • A função de janela lead() tem um recurso muitas vezes esquecido:ele pode fornecer um padrão para quando não existir nenhuma linha inicial. Fornecendo 'infinity' no exemplo. Caso contrário, o último intervalo seria cortado com um limite superior NULL .

Equivalente mínimo


A consulta acima usa um CTE e lead() e sintaxe detalhada. Elegante e talvez mais fácil de entender, mas um pouco mais caro. Aqui está uma versão mais curta, rápida e mínima:
SELECT start_time, count(e.ts) AS events
FROM  (SELECT generate_series(min(ts), max(ts), interval '17 min') FROM event) g(start_time)
LEFT   JOIN event e ON e.ts >= g.start_time
                   AND e.ts <  g.start_time + interval '17 min'
GROUP  BY 1
ORDER  BY 1;

Exemplo para "a cada 15 minutos na última semana"`


E formatação com to_char() .
SELECT to_char(start_time, 'YYYY-MM-DD HH24:MI'), count(e.ts) AS events
FROM   generate_series(date_trunc('day', localtimestamp - interval '7 days')
                     , localtimestamp
                     , interval '15 min') g(start_time)
LEFT   JOIN event e ON e.ts >= g.start_time
                   AND e.ts <  g.start_time + interval '15 min'
GROUP  BY start_time
ORDER  BY start_time;

Ainda ORDER BY e GROUP BY no carimbo de data/hora subjacente valor , não na string formatada. Isso é mais rápido e confiável.

db<>mexa aqui

Resposta relacionada produzindo uma contagem em execução ao longo do tempo:
  • PostgreSQL:contagem de linhas em execução para uma consulta 'por minuto'