Nhóm thành khoảng thời gian 5 phút trong một khoảng thời gian


93

Tôi gặp một số khó khăn với các lệnh mySQL mà tôi muốn thực hiện.

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

Đây là báo cáo đầu ra hiện tại của tôi.

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

Làm cách nào để nhóm chúng thành các kết quả khoảng thời gian 5 phút?

Tôi muốn đầu ra của mình giống như

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 trả lời:


146

Điều này hoạt động với mọi khoảng thời gian.

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

ồ… không nhận được cờ mysql .. đó là một truy vấn postgresql .. nhưng về cơ bản thì điều này cũng có thể thực hiện được với mysql
boecko

2
ok .. thay vì giải nén .. vòng GROUP BY (UNIX_TIMESTAMP (dấu thời gian) / 300) nên thực hiện thủ thuật
boecko

2
@ bình luận Phil là đúng về MySQL bạn nên sử dụng DIV thay vì tròn (/) nếu không thì ranh giới giữa khoảng thời gian là sai
DavidC

1
Chỉ cần thử nó với một số bộ dữ liệu và truy vấn thứ 2 hoạt động tuyệt vời cho MySQL, đó là mối quan tâm của OP. Vì @sky có vẻ vắng mặt, chúng ta có thể nhận được sự đồng thuận của nhóm về việc đây là câu trả lời không?
Joey T

1
Tôi cũng đã thử điều này. bản ghi đầu tiên của nó hiển thị sai mỗi lần cách nhau 2 phút hoặc 3 phút và các khoảng thời gian 5 phút nữa. Lưu ý: - Tôi đã thêm một điều kiện để có được các bản ghi 15 phút qua.
Ritesh

33

Tôi đã gặp vấn đề tương tự.

Tôi thấy rằng thật dễ dàng để nhóm theo bất kỳ khoảng thời gian phút nào chỉ là chia kỷ nguyên theo phút theo số giây và sau đó làm tròn hoặc sử dụng tầng để đi xe phần còn lại. Vì vậy, nếu bạn muốn có khoảng thời gian trong 5 phút, bạn sẽ sử dụng 300 giây .

    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 

Điều này sẽ trả về nhóm dữ liệu chính xác theo khoảng phút đã chọn; tuy nhiên, nó sẽ không trả về khoảng thời gian không chứa bất kỳ dữ liệu nào. Để có được những khoảng trống đó, chúng ta có thể sử dụng hàm create_series .

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

Kết quả:

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   

Bây giờ để có được kết quả với khoảng thời gian không có lần xuất hiện nào, chúng ta chỉ cần kết hợp bên ngoài cả hai tập kết quả .

    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

Kết quả cuối cùng sẽ bao gồm chuỗi có tất cả các khoảng thời gian 5 phút, kể cả những chuỗi không có giá trị.

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 

Có thể dễ dàng thay đổi khoảng thời gian bằng cách điều chỉnh tham số cuối cùng của create_series. Trong trường hợp của chúng tôi, chúng tôi sử dụng '5m' nhưng nó có thể là bất kỳ khoảng nào chúng tôi muốn.


1
Nó sẽ là nếu nó là MySQL. Có vẻ như create_series là một hàm PostgreSQL. Quá tệ.
Andreas

Truy vấn đầu tiên chỉ cung cấp kết quả chỉ dữ liệu hiện tại, nó đếm các bản ghi giữa của 2 khoảng thời gian trong cả hai khoảng thời gian. Giống như trong 2 khoảng thời gian, 10:35 và 10:40, nó tính 10:40 ở cả hai nhóm đó là một trong 10:35 đến 10:40 và 10:40 đến 10:45.
Prem popatia

29

Bạn nên sử dụng GROUP BY UNIX_TIMESTAMP(time_stamp) DIV 300thay vì làm tròn (../ 300) vì làm tròn tôi thấy rằng một số bản ghi được tính thành hai tập kết quả được nhóm lại.


Đây có phải là đúng vòng (../ 300) đã không làm việc đó đúng trên MySQL
DavidC

1
Đối với những người tò mò, DIVtrong MySQL là một floor()bộ phận float an toàn với BIGINTs.
Eric L.

1
Tôi cũng đã thử điều này. bản ghi đầu tiên của nó hiển thị sai mỗi lần cách nhau 2 phút hoặc 3 phút và các khoảng thời gian 5 phút nữa. Lưu ý: - Tôi đã thêm một điều kiện để có được các bản ghi 15 phút qua.
Ritesh

Người ta nên sử dụng TRUNCATE hoặc FLOOR thay vì ROUND vì hành vi làm tròn không được xác định rõ và phụ thuộc vào thư viện C được sử dụng. list.mysql.com/mysql/93613
MrLeeh

28

Đối với postgres , tôi thấy việc sử dụng

date_trunc

chức năng, như:

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

Bạn có thể cung cấp các độ phân giải khác nhau như 'phút', 'giờ', 'ngày' v.v ... cho date_trunc.


7
@tmarthal - nó không nên được ủng hộ. Câu hỏi ban đầu dành cho mysql.
buggedcom

30
Bạn đặt 5ở đâu ở đây trong khoảng thời gian 5 phút?
oldergod

Đối với những điều trên, hãy thay đổi mệnh đề WHERE thành: WHERE dấu thời gian> current_timestamp - khoảng thời gian '5 phút'
Luke Smith

2
Truy vấn này dường như không thực hiện những gì được hỏi, câu hỏi là 'cứ 5 phút một lần' chứ không phải là 5 phút trước đây. câu trả lời phù hợp để được bỏ phiếu từ chối
Mohammed Rafeeq

11

Truy vấn sẽ giống như sau:

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

Có thể bạn sẽ phải chia nhỏ dấu thời gian của mình thành ymd: HM và sử dụng DIV 5 để chia số phút thành các thùng 5 phút - đại loại là

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

... và sau đó tương lai đầu ra trong mã khách hàng để xuất hiện theo cách bạn thích. Hoặc, bạn có thể tạo toàn bộ chuỗi ngày bằng cách sử dụng operatoris sql concat thay vì lấy các cột riêng biệt, nếu bạn muốn.

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

... và sau đó nhóm trên đó


Hmmm ... Nhưng đầu ra không đạt được những gì tôi đang cố gắng có được. Nó trả về một cột và tôi không chắc giá trị của số đếm là bao nhiêu ...
sky

2

Không chắc chắn nếu bạn vẫn cần nó.

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

Còn cái này thì sao:

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

Tôi phát hiện ra rằng với MySQL có lẽ truy vấn chính xác như sau:

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

Cho tôi biết bạn nghĩ gì.


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

Vui lòng cung cấp giải thích cho truy vấn của bạn.
Daniel W.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.