Agrupación en intervalos de 5 minutos dentro de un rango de tiempo


94

Tengo algunas dificultades con los comandos mySQL que quiero hacer.

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

Esta es mi declaración de salida actual.

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

¿Cómo los agrupo en resultados con intervalos de 5 minutos?

Quiero que mi salida sea como

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 

Respuestas:


146

Esto funciona con todos los intervalos.

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 
WHERE …
GROUP BY 
round(extract('epoch' from timestamp) / 300), name


MySQL

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

oh ... no obtuve el indicador mysql ... es una consulta postgresql ... pero básicamente esto también debería ser posible con mysql
— boecko

2
ok .. en vez de extracto .. GRUPO POR redonda (UNIX_TIMESTAMP (fecha y hora) / 300) debe hacer el truco
— boecko

2
El comentario de @ pHiL es correcto en mySql, debe usar DIV en lugar de round (/); de lo contrario, el límite entre intervalos es incorrecto
— DavidC

1
Lo intenté con varios conjuntos de datos y la segunda consulta funciona de manera brillante para MySQL, que era la preocupación de los OP. Dado que @sky parece estar ausente, ¿podemos lograr un consenso grupal sobre esta es la respuesta?
— Joey T

1
También he probado esto. está mostrando el primer registro incorrecto cada vez en intervalos de 2 o 3 minutos y otros intervalos de 5 minutos. Nota: - He agregado una condición para obtener registros de los últimos 15 minutos.
— Ritesh

33

Me encontré con el mismo problema.

Descubrí que es fácil agrupar por cualquier intervalo de minutos, simplemente dividiendo la época por minutos en la cantidad de segundos y luego redondeando o usando el piso para obtener el resto. Entonces, si desea obtener un intervalo en 5 minutos , usaría 300 segundos .

    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 

Esto devolverá los datos agrupados correctamente por el intervalo de minutos seleccionado; sin embargo, no devolverá los intervalos que no contengan ningún dato. Para obtener esos intervalos vacíos podemos usar la función generate_series .

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

Resultado:

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   

Ahora, para obtener el resultado con intervalo con cero ocurrencias, simplemente unimos ambos conjuntos de resultados .

    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

El resultado final incluirá la serie con todos los intervalos de 5 minutos, incluso aquellos que no tienen valores.

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 

El intervalo se puede cambiar fácilmente ajustando el último parámetro de generate_series. En nuestro caso usamos '5m' pero podría ser cualquier intervalo que queramos.


1
Lo habría sido si fuera MySQL. Parece que generate_series es una función de PostgreSQL. Demasiado.
— Andreas

La primera consulta que solo da un resultado de datos actuales, cuenta los registros intermedios de 2 períodos de tiempo en ambos períodos de tiempo. Como en el período de tiempo 2, 10:35 y 10:40, cuenta 10:40 en ambos grupos, que es uno en 10:35 a 10:40 y 10:40 a 10:45.
— Prem popatia

29

Debería usar en GROUP BY UNIX_TIMESTAMP(time_stamp) DIV 300lugar de redondear (../ 300) debido al redondeo. Descubrí que algunos registros se cuentan en dos conjuntos de resultados agrupados.


Esto es correcto, la ronda (../ 300) no lo estaba haciendo correctamente en mySql
— DavidC

1
Para aquellos que tengan curiosidad, DIVen MySQL hay una floor()división flotante que es segura con BIGINTs.
— Eric L.

1
También he probado esto. está mostrando el primer registro incorrecto cada vez en intervalos de 2 o 3 minutos y otros intervalos de 5 minutos. Nota: - He agregado una condición para obtener registros de los últimos 15 minutos.
— Ritesh

Se debe usar TRUNCATE o FLOOR en lugar de ROUND porque el comportamiento de redondeo no está bien definido y depende de la biblioteca C utilizada. list.mysql.com/mysql/93613
— MrLeeh

28

Para postgres , encontré más fácil y más preciso usar el

date_trunc

función, como:

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

Puede proporcionar varias resoluciones como 'minuto', 'hora', 'día', etc. a date_trunc.


7
@tmarthal: no se debería votar a favor. La pregunta original era para mysql.
— buggedcom

30
¿Dónde establece 5aquí el intervalo de 5 minutos?
— oldgod

Para lo anterior, cambie la cláusula WHERE a: WHERE timestamp> current_timestamp - intervalo '5 minutos'
— Luke Smith

2
Esta consulta no parece hacer lo que se pide, la pregunta es 'cada 5' minutos, no 5 minutos antes. respuesta digna de ser votada en contra
— Mohammed Rafeeq

11

La consulta será algo como:

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

Probablemente tendrá que dividir su marca de tiempo en ymd: HM y usar DIV 5 para dividir los minutos en contenedores de 5 minutos, algo así como

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

... y luego coloque el resultado en el código del cliente para que aparezca de la forma que desee. O puede crear la cadena de fecha completa utilizando el operador sql concat en lugar de obtener columnas separadas, si lo desea.

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

... y luego agrupar en eso


Hmmm ... Pero la salida no está obteniendo lo que estoy tratando de obtener. Devuelve una columna y no estoy muy seguro de cuál es el valor del recuento ...
— cielo

2

No estoy seguro de si aún lo necesita.

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

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

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

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

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

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


1

Que tal este:

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

Descubrí que con MySQL probablemente la consulta correcta es la siguiente:

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

Déjame saber lo que piensas.


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))

Proporcione una explicación a su consulta.
— Daniel W.
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.