PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Il modo migliore per contare i record in base a intervalli di tempo arbitrari in Rails+Postgres

Fortunatamente, stai usando PostgreSQL. La funzione della finestra generate_series() è tuo amico.

Caso di prova

Data la seguente tabella di test (che tu avrebbe dovuto fornire):

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';

Un evento ogni 7 minuti (più da 0 a 7 minuti, in modo casuale).

Soluzione di base

Questa query conta gli eventi per qualsiasi intervallo di tempo arbitrario. 17 minuti nell'esempio:

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;
  • La query recupera ts minimo e massimo dalla tavola di base per coprire l'intera fascia oraria. Puoi invece utilizzare un intervallo di tempo arbitrario.

  • Fornisci qualsiasi intervallo di tempo secondo necessità.

  • Produce una riga per ogni fascia oraria. Se non si è verificato alcun evento durante quell'intervallo, il conteggio è 0 .

  • Assicurati di gestire il limite superiore e inferiore correttamente:

    • Risultati imprevisti da query SQL con timestamp BETWEEN
  • La funzione della finestra lead() ha una caratteristica spesso trascurata:può fornire un valore predefinito quando non esiste una riga iniziale. Fornire 'infinity' nell'esempio. Altrimenti l'ultimo intervallo verrebbe interrotto con un limite superiore NULL .

Equivalente minimo

La query precedente utilizza un CTE e lead() e sintassi prolissa. Elegante e forse più facile da capire, ma un po' più costoso. Ecco una versione più breve, più veloce e minimale:

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;

Esempio per "ogni 15 minuti nell'ultima settimana"`

E la formattazione con 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;

Ancora ORDER BY e GROUP BY sul valore del timestamp sottostante , non sulla stringa formattata. È più veloce e più affidabile.

db<>gioca qui

Risposta correlata che produce un conteggio progressivo nel periodo di tempo:

  • PostgreSQL:conteggio in esecuzione delle righe per una query 'per minuto'