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 
cielo
fuente

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 
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
boecko
fuente
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.

Néstor Martínez
fuente
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.

pHiL
fuente
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.

Scott Persinger
fuente
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
WASD42
fuente
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

Bill Dueber
fuente
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 |

usuario7088930
fuente
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
;
mac13k
fuente
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.

EBAH
fuente
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))
gerrit bosua
fuente
Proporcione una explicación a su consulta.
Daniel W.