Raggruppamento in intervalli di 5 minuti all'interno di un intervallo di tempo


94

Ho alcune difficoltà con i comandi mySQL che voglio eseguire.

SELECT a.timestamp, name, count(b.name) 
FROM time a, id b 
WHERE a.user = b.user
  AND a.id = b.id
  AND b.name = 'John'
  AND a.timestamp BETWEEN '2010-11-16 10:30:00' AND '2010-11-16 11:00:00' 
GROUP BY a.timestamp

Questa è la mia dichiarazione di output corrente.

timestamp            name  count(b.name)
-------------------  ----  -------------
2010-11-16 10:32:22  John  2
2010-11-16 10:35:12  John  7
2010-11-16 10:36:34  John  1
2010-11-16 10:37:45  John  2
2010-11-16 10:48:26  John  8
2010-11-16 10:55:00  John  9
2010-11-16 10:58:08  John  2

Come li raggruppo in risultati a intervalli di 5 minuti?

Voglio che la mia uscita sia simile

timestamp            name  count(b.name)
-------------------  ----  -------------
2010-11-16 10:30:00  John  2
2010-11-16 10:35:00  John  10
2010-11-16 10:40:00  John  0
2010-11-16 10:45:00  John  8
2010-11-16 10:50:00  John  0
2010-11-16 10:55:00  John  11 

Risposte:


146

Funziona con ogni intervallo.

PostgreSQL

SELECT
    TIMESTAMP WITH TIME ZONE 'epoch' +
    INTERVAL '1 second' * round(extract('epoch' from timestamp) / 300) * 300 as timestamp,
    name,
    count(b.name)
FROM time a, id 
WHEREGROUP BY 
round(extract('epoch' from timestamp) / 300), name


MySQL

SELECT
    timestamp,  -- not sure about that
    name,
    count(b.name)
FROM time a, id 
WHEREGROUP BY 
UNIX_TIMESTAMP(timestamp) DIV 300, name

oh ... non ho ricevuto il flag mysql .. è una query postgresql .. ma fondamentalmente questo dovrebbe essere possibile anche con mysql
boecko

2
ok .. invece di estrarre .. GROUP BY round (UNIX_TIMESTAMP (timestamp) / 300) dovrebbe fare il trucco
boecko

2
Il commento di @ pHiL è corretto su mySql dovresti usare DIV invece di round (/) altrimenti il ​​limite tra gli intervalli è sbagliato
DavidC

1
Ho appena provato con diversi set di dati e la seconda query funziona brillantemente per MySQL, che era la preoccupazione degli OP. Dato che @sky sembra assente, possiamo ottenere un consenso di gruppo su questa è la risposta?
Joey T

1
Ho provato anche questo. mostra il primo record sbagliato ogni volta che si verificano intervalli di 2 minuti o 3 minuti e ulteriori intervalli di 5 minuti. Nota: - ho aggiunto una condizione per ottenere i record degli ultimi 15 minuti.
Ritesh

33

Mi sono imbattuto nello stesso problema.

Ho scoperto che è facile raggruppare in base a intervalli di un minuto, semplicemente dividendo l' epoca per minuti in secondi e quindi arrotondando o usando il pavimento per ottenere il giro del resto. Quindi, se vuoi ottenere l'intervallo in 5 minuti, dovresti usare 300 secondi .

    SELECT COUNT(*) cnt, 
    to_timestamp(floor((extract('epoch' from timestamp_column) / 300 )) * 300) 
    AT TIME ZONE 'UTC' as interval_alias
    FROM TABLE_NAME GROUP BY interval_alias
interval_alias       cnt
-------------------  ----  
2010-11-16 10:30:00  2
2010-11-16 10:35:00  10
2010-11-16 10:45:00  8
2010-11-16 10:55:00  11 

Ciò restituirà i dati correttamente raggruppati per l'intervallo di minuti selezionato; tuttavia, non restituirà gli intervalli che non contengono dati. Per ottenere quegli intervalli vuoti possiamo usare la funzione generate_series .

    SELECT generate_series(MIN(date_trunc('hour',timestamp_column)),
    max(date_trunc('minute',timestamp_column)),'5m') as interval_alias FROM 
    TABLE_NAME

Risultato:

interval_alias       
-------------------    
2010-11-16 10:30:00  
2010-11-16 10:35:00
2010-11-16 10:40:00   
2010-11-16 10:45:00
2010-11-16 10:50:00   
2010-11-16 10:55:00   

Ora per ottenere il risultato con intervallo con zero occorrenze, dobbiamo semplicemente unire entrambi i set di risultati .

    SELECT series.minute as interval,  coalesce(cnt.amnt,0) as count from 
       (
       SELECT count(*) amnt,
       to_timestamp(floor((extract('epoch' from timestamp_column) / 300 )) * 300)
       AT TIME ZONE 'UTC' as interval_alias
       from TABLE_NAME  group by interval_alias
       ) cnt
    
    RIGHT JOIN 
       (    
       SELECT generate_series(min(date_trunc('hour',timestamp_column)),
       max(date_trunc('minute',timestamp_column)),'5m') as minute from TABLE_NAME 
       ) series
  on series.minute = cnt.interval_alias

Il risultato finale includerà la serie con tutti gli intervalli di 5 minuti anche quelli che non hanno valori.

interval             count
-------------------  ----  
2010-11-16 10:30:00  2
2010-11-16 10:35:00  10
2010-11-16 10:40:00  0
2010-11-16 10:45:00  8
2010-11-16 10:50:00  0 
2010-11-16 10:55:00  11 

L'intervallo può essere facilmente modificato regolando l'ultimo parametro di generate_series. Nel nostro caso usiamo "5m" ma potrebbe essere qualsiasi intervallo desideriamo.


1
Lo sarebbe stato se fosse MySQL. Sembra che generate_series sia una funzione PostgreSQL. Peccato.
Andreas

La prima query che fornisce solo il risultato dei dati presenti, conta i record intermedi di 2 periodi di tempo in entrambi i periodi di tempo. Come in 2 periodi di tempo, 10:35 e 10:40, conta 10:40 in entrambi i gruppi, uno tra 10:35 e 10:40 e 10:40 e 10:45.
Prem popatia

29

Dovresti piuttosto usare GROUP BY UNIX_TIMESTAMP(time_stamp) DIV 300invece di round (../ 300) perché ho scoperto che alcuni record vengono conteggiati in due set di risultati raggruppati.


È corretto, il round (../ 300) non lo stava facendo correttamente su mySql
DavidC

1
Per coloro che sono curiosi, DIVin MySQL è una floor()divisione in virgola mobile che è sicura con BIGINTs.
Eric L.

1
Ho provato anche questo. mostra il primo record sbagliato ogni volta che si verificano intervalli di 2 minuti o 3 minuti e ulteriori intervalli di 5 minuti. Nota: - ho aggiunto una condizione per ottenere i record degli ultimi 15 minuti.
Ritesh

Si dovrebbe usare TRUNCATE o FLOOR invece di ROUND perché il comportamento di arrotondamento non è ben definito e dipende dalla libreria C utilizzata. lists.mysql.com/mysql/93613
MrLeeh

28

Per postgres , ho trovato più facile e preciso usare il file

date_trunc

funzione, come:

select name, sum(count), date_trunc('minute',timestamp) as timestamp
FROM table
WHERE xxx
GROUP BY name,date_trunc('minute',timestamp)
ORDER BY timestamp

Puoi fornire varie risoluzioni come 'minuto', 'ora', 'giorno' ecc ... a date_trunc.


7
@tmarthal - non dovrebbe essere votato. La domanda originale era per mysql.
buggedcom

30
Dove si imposta il 5qui per l'intervallo di 5 minuti?
oldergod

Per quanto sopra, cambia la clausola WHERE in: WHERE timestamp> current_timestamp - intervallo "5 minuti"
Luke Smith

2
Questa query non sembra fare ciò che viene chiesto, la domanda è "ogni 5" minuti non 5 minuti prima di adesso. risposta adatta per essere sottovalutata
Mohammed Rafeeq

11

La query sarà qualcosa del tipo:

SELECT 
  DATE_FORMAT(
    MIN(timestamp),
    '%d/%m/%Y %H:%i:00'
  ) AS tmstamp,
  name,
  COUNT(id) AS cnt 
FROM
  table
GROUP BY ROUND(UNIX_TIMESTAMP(timestamp) / 300), name

4

Probabilmente dovrai suddividere il tuo timestamp in ymd: HM e usare DIV 5 per dividere i minuti in contenitori da 5 minuti, qualcosa di simile

select year(a.timestamp), 
       month(a.timestamp), 
       hour(a.timestamp), 
       minute(a.timestamp) DIV 5,
       name, 
       count(b.name)
FROM time a, id b
WHERE a.user = b.user AND a.id = b.id AND b.name = 'John' 
      AND a.timestamp BETWEEN '2010-11-16 10:30:00' AND '2010-11-16 11:00:00'
GROUP BY year(a.timestamp), 
       month(a.timestamp), 
       hour(a.timestamp), 
       minute(a.timestamp) DIV 12

... e poi futz l'output nel codice client per apparire come piace a te. Oppure, puoi creare l'intera stringa della data utilizzando l'operatore sql concat invece di ottenere colonne separate, se lo desideri.

select concat(year(a.timestamp), "-", month(a.timestamp), "-" ,day(a.timestamp), 
       " " , lpad(hour(a.timestamp),2,'0'), ":", 
       lpad((minute(a.timestamp) DIV 5) * 5, 2, '0'))

... e poi gruppo su quello


Hmmm ... Ma l'output non sta ottenendo quello che sto cercando di ottenere. Restituisce una colonna e non sono molto sicuro di quale sia il valore del conteggio ...
cielo

2

Non sono sicuro se ne hai ancora bisogno.

SELECT FROM_UNIXTIME(FLOOR((UNIX_TIMESTAMP(timestamp))/300)*300) AS t,timestamp,count(1) as c from users GROUP BY t ORDER BY t;

29-10-2016 19:35:00 | 29-10-2016 19:35:50 | 4 |

29-10-2016 19:40:00 | 29-10-2016 19:40:37 | 5 |

29-10-2016 19:45:00 | 29-10-2016 19:45:09 | 6 |

29-10-2016 19:50:00 | 29-10-2016 19:51:14 | 4 |

29-10-2016 19:55:00 | 29-10-2016 19:56:17 | 1 |


1

Che ne dici di questo:

select 
    from_unixtime(unix_timestamp(timestamp) - unix_timestamp(timestamp) mod 300) as ts,  
    sum(value)
from group_interval 
group by ts 
order by ts
;

0

Ho scoperto che con MySQL probabilmente la query corretta è la seguente:

SELECT SUBSTRING( FROM_UNIXTIME( CEILING( timestamp /300 ) *300,  
                                 '%Y-%m-%d %H:%i:%S' ) , 1, 19 ) AS ts_CEILING,
SUM(value)
FROM group_interval
GROUP BY SUBSTRING( FROM_UNIXTIME( CEILING( timestamp /300 ) *300,  
                                   '%Y-%m-%d %H:%i:%S' ) , 1, 19 )
ORDER BY SUBSTRING( FROM_UNIXTIME( CEILING( timestamp /300 ) *300,  
                                   '%Y-%m-%d %H:%i:%S' ) , 1, 19 ) DESC

Fatemi sapere cosa ne pensate.


0
select 
CONCAT(CAST(CREATEDATE AS DATE),' ',datepart(hour,createdate),':',ROUNd(CAST((CAST((CAST(DATEPART(MINUTE,CREATEDATE) AS DECIMAL (18,4)))/5 AS INT)) AS DECIMAL (18,4))/12*60,2)) AS '5MINDATE'
,count(something)
from TABLE
group by CONCAT(CAST(CREATEDATE AS DATE),' ',datepart(hour,createdate),':',ROUNd(CAST((CAST((CAST(DATEPART(MINUTE,CREATEDATE) AS DECIMAL (18,4)))/5 AS INT)) AS DECIMAL (18,4))/12*60,2))

Fornisci una spiegazione alla tua richiesta.
Daniel W.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.