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

PostgreSQL:conteggio in esecuzione delle righe per una query "per minuto"

Restituisci solo minuti con attività

Il più corto

SELECT DISTINCT
       date_trunc('minute', "when") AS minute
     , count(*) OVER (ORDER BY date_trunc('minute', "when")) AS running_ct
FROM   mytable
ORDER  BY 1;

Usa date_trunc() , restituisce esattamente ciò di cui hai bisogno.

Non includere id nella query, poiché vuoi GROUP BY fette minute.

count() è tipicamente usato come semplice funzione aggregata. Aggiunta di un OVER clausola lo rende una funzione di finestra. Ometti PARTITION BY nella definizione della finestra - vuoi un conteggio continuo su tutte le righe . Per impostazione predefinita, conta dalla prima riga all'ultimo peer della riga corrente come definito da ORDER BY . Il manuale:

L'opzione di inquadratura predefinita è RANGE UNBOUNDED PRECEDING , che è lo stesso di RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW . Con ORDER BY , questo imposta il frame in modo che sia tutte le righe dall'inizio della partizione fino all'ultimo ORDER BY della riga corrente pari.

E questo è esattamente quello che ti serve.

Usa count(*) anziché count(id) . Si adatta meglio alla tua domanda ("conteggio di righe"). In genere è leggermente più veloce di count(id) . E, mentre potremmo supporre che id è NOT NULL , non è stato specificato nella domanda, quindi count(id) è sbagliato , in senso stretto, perché i valori NULL non vengono conteggiati con count(id) .

Non puoi GROUP BY porzioni di minuti allo stesso livello di query. Le funzioni aggregate vengono applicate prima funzioni della finestra, la funzione della finestra count(*) vedrebbe solo 1 riga al minuto in questo modo.
Puoi, tuttavia, SELECT DISTINCT , perché DISTINCT viene applicato dopo funzioni della finestra.

ORDER BY 1 è solo un'abbreviazione per ORDER BY date_trunc('minute', "when") qui.
1 è un riferimento di riferimento posizionale alla prima espressione in SELECT elenco.

Usa to_char() se è necessario formattare il risultato. Come:

SELECT DISTINCT
       to_char(date_trunc('minute', "when"), 'DD.MM.YYYY HH24:MI') AS minute
     , count(*) OVER (ORDER BY date_trunc('minute', "when")) AS running_ct
FROM   mytable
ORDER  BY date_trunc('minute', "when");

Il più veloce

SELECT minute, sum(minute_ct) OVER (ORDER BY minute) AS running_ct
FROM  (
   SELECT date_trunc('minute', "when") AS minute
        , count(*) AS minute_ct
   FROM   tbl
   GROUP  BY 1
   ) sub
ORDER  BY 1;

Molto simile a quanto sopra, ma:

Uso una sottoquery per aggregare e contare le righe al minuto. In questo modo otteniamo 1 riga al minuto senza DISTINCT nel SELECT esterno .

Usa sum() come funzione di aggregazione della finestra ora per sommare i conteggi dalla sottoquery.

Ho scoperto che questo è sostanzialmente più veloce con molte righe al minuto.

Includi minuti senza attività

Il più corto

@GabiMe ha chiesto in un commento come ottenere una riga per ogni minute nell'intervallo di tempo, compresi quelli in cui non si è verificato alcun evento (nessuna riga nella tabella di base):

SELECT DISTINCT
       minute, count(c.minute) OVER (ORDER BY minute) AS running_ct
FROM  (
   SELECT generate_series(date_trunc('minute', min("when"))
                        ,                      max("when")
                        , interval '1 min')
   FROM   tbl
   ) m(minute)
LEFT   JOIN (SELECT date_trunc('minute', "when") FROM tbl) c(minute) USING (minute)
ORDER  BY 1;

Genera una riga per ogni minuto nell'intervallo di tempo compreso tra il primo e l'ultimo evento con generate_series() - qui direttamente in base ai valori aggregati della sottoquery.

LEFT JOIN a tutti i timestamp troncati al minuto e contare. NULL i valori (dove non esiste alcuna riga) non vengono aggiunti al conteggio corrente.

Il più veloce

Con CTE:

WITH cte AS (
   SELECT date_trunc('minute', "when") AS minute, count(*) AS minute_ct
   FROM   tbl
   GROUP  BY 1
   ) 
SELECT m.minute
     , COALESCE(sum(cte.minute_ct) OVER (ORDER BY m.minute), 0) AS running_ct
FROM  (
   SELECT generate_series(min(minute), max(minute), interval '1 min')
   FROM   cte
   ) m(minute)
LEFT   JOIN cte USING (minute)
ORDER  BY 1;

Ancora una volta, aggrega e conta le righe al minuto nel primo passaggio, omette la necessità di un successivo DISTINCT .

Diverso da count() , sum() può restituire NULL . Il valore predefinito è 0 con COALESCE .

Con molte righe e un indice su "when" questa versione con una sottoquery è stata la più veloce tra un paio di varianti che ho testato con Postgres 9.1 - 9.4:

SELECT m.minute
     , COALESCE(sum(c.minute_ct) OVER (ORDER BY m.minute), 0) AS running_ct
FROM  (
   SELECT generate_series(date_trunc('minute', min("when"))
                        ,                      max("when")
                        , interval '1 min')
   FROM   tbl
   ) m(minute)
LEFT   JOIN (
   SELECT date_trunc('minute', "when") AS minute
        , count(*) AS minute_ct
   FROM   tbl
   GROUP  BY 1
   ) c USING (minute)
ORDER  BY 1;