Agrupando em intervalos de 5 minutos dentro de um intervalo de tempo

91

Tenho algumas dificuldades com os comandos mySQL que desejo executar.

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 é minha declaração de saída atual.

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

Como faço para agrupá-los em resultados de intervalo de 5 minutos?

Eu quero que minha saída seja 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 
céu
fonte

Respostas:

144

Isso funciona com todos os 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
boecko
fonte
oh… não recebi o sinalizador mysql .. é uma consulta postgresql .. mas basicamente isso também deve ser possível com o mysql
boecko
2
ok .. em vez de extrair .. Rodada GROUP BY (UNIX_TIMESTAMP (timestamp) / 300) deve
resolver
2
O comentário de @phiL está correto em mySql, você deve usar DIV em vez de arredondar (/), caso contrário, o limite entre os intervalos está errado
DavidC
1
Apenas tentei com vários conjuntos de dados e a segunda consulta funciona perfeitamente para MySQL, que era a preocupação dos OPs. Já que @sky parece ausente, podemos obter um consenso do grupo sobre esta é a resposta?
Joey T
1
Eu também tentei isso. está mostrando o primeiro registro errado a cada intervalo de 2 ou 3 minutos e mais intervalos de 5 minutos. Observação: - adicionei uma condição para obter os registros dos últimos 15 minutos.
Ritesh
32

Eu me deparei com o mesmo problema.

Descobri que é fácil agrupar por qualquer intervalo de minuto, basta dividir a época por minutos em quantidade de segundos e, em seguida, arredondar ou usar o chão para se livrar do restante. Portanto, se você quiser obter o intervalo em 5 minutos, deverá usar 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 

Isso retornará os dados agrupados corretamente pelo intervalo de minutos selecionado; no entanto, ele não retornará os intervalos que não contêm dados. Para obter esses intervalos vazios, podemos usar a função 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   

Agora, para obter o resultado com intervalo com zero ocorrências, apenas juntamos os dois 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

O resultado final incluirá a série com todos os intervalos de 5 minutos, mesmo aqueles que não têm 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 

O intervalo pode ser facilmente alterado ajustando o último parâmetro de generate_series. No nosso caso, usamos '5m', mas pode ser qualquer intervalo que quisermos.

Nestor Martinez
fonte
1
Teria sido se fosse MySQL. Parece que generate_series é uma função PostgreSQL. Que pena.
Andreas
A primeira consulta que está fornecendo apenas o resultado dos dados presentes, conta os registros intermediários de 2 períodos de tempo em ambos os períodos. Como em 2 períodos de tempo, 10:35 e 10:40, ele conta 10:40 em ambos os grupos, que é um em 10:35 a 10:40 e 10:40 a 10:45.
Prem popatia
29

Você deve preferir usar em GROUP BY UNIX_TIMESTAMP(time_stamp) DIV 300vez de arredondar (../ 300) por causa do arredondamento que descobri que alguns registros são contados em dois conjuntos de resultados agrupados.

pHiL
fonte
Isto está correto, a rodada (../ 300) não estava funcionando corretamente no mySql
DavidC
1
Para quem está curioso, DIVno MySQL é uma floor()divisão de float que é segura com BIGINTs.
Eric L.
1
Eu também tentei isso. está mostrando o primeiro registro errado a cada intervalo de 2 ou 3 minutos e mais intervalos de 5 minutos. Observação: - adicionei uma condição para obter os registros dos últimos 15 minutos.
Ritesh
Deve-se usar TRUNCATE ou FLOOR em vez de ROUND porque o comportamento do arredondamento não está bem definido e depende da biblioteca C usada. lists.mysql.com/mysql/93613
MrLeeh
28

Para postgres , achei mais fácil e preciso usar o

date_trunc

função, 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

Você pode fornecer várias resoluções como 'minuto', 'hora', 'dia' etc ... até date_trunc.

Scott Persinger
fonte
7
@tmarthal - não deve ser votado a favor. A questão original era para o mysql.
buggedcom
30
Onde você define o 5aqui para o intervalo de 5 minutos?
oldergod
Para o acima, altere a cláusula WHERE para: WHERE timestamp> current_timestamp - intervalo '5 minutos'
Luke Smith
2
Esta consulta parece não fazer o que é perguntado, a pergunta é 'a cada 5' minutos, não 5 minutos antes. resposta apta a ser rejeitada
Mohammed Rafeeq
11

A 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
fonte
4

Você provavelmente terá que dividir seu carimbo de data / hora em ymd: HM e usar DIV 5 para dividir os minutos em caixas de 5 minutos - algo 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

... e, em seguida, futz a saída no código do cliente para aparecer da maneira que você gosta. Ou você pode construir toda a string de data usando o operador sql concat em vez de obter colunas separadas, se desejar.

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 então agrupar nisso

Bill Dueber
fonte
Hmmm ... Mas a saída não está obtendo o que estou tentando obter. Ele retorna uma coluna e eu não tenho muita certeza de qual é o valor da contagem ...
céu
2

Não tenho certeza se você ainda precisa disso.

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

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

user7088930
fonte
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
fonte
0

Descobri que com o MySQL provavelmente a consulta correta é a seguinte:

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

Diz-me o que pensas.

EBAH
fonte
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
fonte
Forneça uma explicação para sua consulta.
Daniel W.