2011-08-17 45 views
8

Tôi có bảng cơ sở dữ liệu chứa thông tin đăng ký của mỗi người dùng ở các thành phố. Tôi cần biết số ngày người dùng ở trong thành phố và sau đó, số lượt truy cập mà người dùng đã thực hiện cho một thành phố (một lượt truy cập bao gồm các ngày liên tiếp được chi tiêu trong một thành phố).MySQL: nhóm theo ngày liên tiếp và đếm nhóm

Vì vậy, hãy xem xét tôi có bảng sau (đơn giản, chỉ chứa các DATETIME s - cùng sử dụng và thành phố):

 datetime 
------------------- 
2011-06-30 12:11:46 
2011-07-01 13:16:34 
2011-07-01 15:22:45 
2011-07-01 22:35:00 
2011-07-02 13:45:12 
2011-08-01 00:11:45 
2011-08-05 17:14:34 
2011-08-05 18:11:46 
2011-08-06 20:22:12 

Số ngày thành viên này đã từng đến thành phố này sẽ là (30.06, 01.07, 02.07, 01.08, 05.08, 06.08).

Tôi nghĩ để làm điều này bằng SELECT COUNT(id) FROM table GROUP BY DATE(datetime)

Sau đó, cho số lượng người dùng truy cập này đã làm cho thành phố này, truy vấn sẽ trả về (30.06-02.07, 01.08, 05.08 -06.08).

Vấn đề là tôi không biết làm cách nào để tạo truy vấn này.

Mọi trợ giúp sẽ được đánh giá cao!

Trả lời

10

Bạn có thể tìm thấy những ngày đầu tiên của mỗi khi truy cập bằng cách tìm checkins nơi không có checkin ngày hôm trước.

select count(distinct date(start_of_visit.datetime)) 
from checkin start_of_visit 
left join checkin previous_day 
    on start_of_visit.user = previous_day.user 
    and start_of_visit.city = previous_day.city 
    and date(start_of_visit.datetime) - interval 1 day = date(previous_day.datetime) 
where previous_day.id is null 

Có một số phần quan trọng trong truy vấn này.

Trước tiên, mỗi lần đăng ký được tham gia vào bất kỳ đăng ký nào từ ngày hôm trước. Nhưng vì nó là một tham gia bên ngoài, nếu không có kiểm tra ngày hôm trước, phía bên phải của tham gia sẽ có kết quả NULL. Việc lọc WHERE xảy ra sau khi tham gia, do đó, nó chỉ giữ những kiểm tra đó từ phía bên trái, nơi không có từ phía bên phải. LEFT OUTER JOIN/WHERE IS NULL thực sự hữu ích khi tìm kiếm mọi thứ không phải là.

Sau đó, số này tính riêng biệt ngày đăng ký để đảm bảo rằng nó không bị tính hai lần nếu người dùng đã đăng ký nhiều lần vào ngày đầu tiên của lượt truy cập. (Tôi thực sự đã thêm phần đó vào chỉnh sửa, khi tôi phát hiện lỗi có thể.)

Chỉnh sửa: Tôi vừa đọc lại truy vấn được đề xuất của bạn cho câu hỏi đầu tiên. Truy vấn của bạn sẽ cho bạn số lượng đăng ký vào một ngày cụ thể, thay vì số ngày. Tôi nghĩ bạn muốn một cái gì đó như thế này thay thế:

select count(distinct date(datetime)) 
from checkin 
where user='some user' and city='some city' 
+0

Về khía cạnh đầu tiên ... Tôi dường như không hiểu hoàn toàn đề xuất của bạn ... để cung cấp thêm một số chi tiết? Cảm ơn bạn! Về câu hỏi thứ hai, truy vấn của tôi là đúng, miễn là bạn không tính người dùng và thành phố, như được đề cập trong câu hỏi của tôi. – linkyndy

+0

Xin lỗi, tôi giả định rằng kết quả cho "số ngày người dùng ở trong thành phố" sẽ trông giống như (user_id, count_of_days). – Simon

+0

Cảm ơn bạn đã biết chi tiết. Với một số điều chỉnh để phù hợp với bảng cơ sở dữ liệu thực tế của tôi, truy vấn của bạn hoạt động như một sự quyến rũ. Cám ơn bạn một lần nữa! – linkyndy

0

cho một tiểu nhiệm vụ đầu tiên:

select count(*) 
from (
select TO_DAYS(p.d) 
from p 
group by TO_DAYS(p.d) 
) t 
0

Tôi nghĩ bạn nên cân nhắc việc thay đổi cấu trúc cơ sở dữ liệu. Bạn có thể thêm lượt truy cập bảng và visit_id vào bảng đăng ký của mình. Mỗi khi bạn muốn đăng ký checkin mới, bạn kiểm tra nếu có bất kỳ checkin một ngày trở lại. Nếu có thì bạn thêm một checkin mới với visit_id từ checkin ngày hôm qua.Nếu không, bạn thêm lượt truy cập mới vào lượt truy cập và đăng ký mới với visit_id mới.

Sau đó, bạn có thể giúp bạn có được dữ liệu trong một truy vấn với một cái gì đó như thế: SELECT COUNT(id) AS number_of_days, COUNT(DISTINCT visit_id) number_of_visits FROM checkin GROUP BY user, city

Đó là không phải là rất tối ưu nhưng vẫn tốt hơn là làm gì với cấu trúc hiện tại và nó sẽ làm việc. Ngoài ra nếu kết quả có thể là các truy vấn riêng biệt, nó sẽ hoạt động rất nhanh. Tuy nhiên, tất nhiên là bạn cần phải thay đổi cấu trúc cơ sở dữ liệu, thực hiện thêm một số kịch bản và chuyển đổi dữ liệu hiện tại sang cấu trúc mới (tức là bạn sẽ cần phải thêm dữ liệu hiện tại vào dữ liệu hiện tại).

+0

Cảm ơn bạn đã trả lời, nhưng tôi muốn gắn bó với cấu trúc cơ sở dữ liệu hiện tại của tôi, ít nhất là bây giờ. Ngoài ra tôi sẽ cần phải thực hiện thêm một số thao tác khi chèn, vì một ngày có thể có nhiều đăng ký, do đó, nó không đơn giản như vậy với "kiểm tra nếu có bất kỳ checkin một ngày trở lại". Kiểu thao tác dữ liệu này cũng có thể được thực hiện bằng PHP với cấu trúc cơ sở dữ liệu được cung cấp, nhưng tôi đang tìm kiếm một truy vấn để thực hiện công việc này, vì nó sạch hơn và thuận tiện hơn. – linkyndy

3

Cố gắng áp dụng mã này để công việc của bạn -

CREATE TABLE visits(
    user_id INT(11) NOT NULL, 
    dt DATETIME DEFAULT NULL 
); 

INSERT INTO visits VALUES 
    (1, '2011-06-30 12:11:46'), 
    (1, '2011-07-01 13:16:34'), 
    (1, '2011-07-01 15:22:45'), 
    (1, '2011-07-01 22:35:00'), 
    (1, '2011-07-02 13:45:12'), 
    (1, '2011-08-01 00:11:45'), 
    (1, '2011-08-05 17:14:34'), 
    (1, '2011-08-05 18:11:46'), 
    (1, '2011-08-06 20:22:12'), 
    (2, '2011-08-30 16:13:34'), 
    (2, '2011-08-31 16:13:41'); 


SET @i = 0; 
SET @last_dt = NULL; 
SET @last_user = NULL; 

SELECT v.user_id, 
    COUNT(DISTINCT(DATE(dt))) number_of_days, 
    MAX(days) number_of_visits 
FROM 
    (SELECT user_id, dt 
     @i := IF(@last_user IS NULL OR @last_user <> user_id, 1, IF(@last_dt IS NULL OR (DATE(dt) - INTERVAL 1 DAY) > DATE(@last_dt), @i + 1, @i)) AS days, 
     @last_dt := DATE(dt), 
     @last_user := user_id 
    FROM 
    visits 
    ORDER BY 
    user_id, dt 
) v 
GROUP BY 
    v.user_id; 

---------------- 
Output: 

+---------+----------------+------------------+ 
| user_id | number_of_days | number_of_visits | 
+---------+----------------+------------------+ 
|  1 |    6 |    3 | 
|  2 |    2 |    1 | 
+---------+----------------+------------------+ 

Giải thích:

Để hiểu cách thức hoạt động chúng ta hãy kiểm tra các subquery, ở đây nó được.

SET @i = 0; 
SET @last_dt = NULL; 
SET @last_user = NULL; 


SELECT user_id, dt, 
     @i := IF(@last_user IS NULL OR @last_user <> user_id, 1, IF(@last_dt IS NULL OR (DATE(dt) - INTERVAL 1 DAY) > DATE(@last_dt), @i + 1, @i)) AS 

days, 
     @last_dt := DATE(dt) lt, 
     @last_user := user_id lu 
FROM 
    visits 
ORDER BY 
    user_id, dt; 

Khi bạn thấy truy vấn trả về tất cả các hàng và thực hiện xếp hạng cho số lượt truy cập. Đây là phương thức xếp hạng được biết dựa trên các biến, lưu ý rằng các hàng được sắp xếp theo trường người dùng và ngày tháng. Truy vấn này tính toán dùng truy cập, và kết quả tiếp theo dữ liệu thiết lập nơi days cột cung cấp thứ hạng cho số lần truy cập -

+---------+---------------------+------+------------+----+ 
| user_id | dt     | days | lt   | lu | 
+---------+---------------------+------+------------+----+ 
|  1 | 2011-06-30 12:11:46 | 1 | 2011-06-30 | 1 | 
|  1 | 2011-07-01 13:16:34 | 1 | 2011-07-01 | 1 | 
|  1 | 2011-07-01 15:22:45 | 1 | 2011-07-01 | 1 | 
|  1 | 2011-07-01 22:35:00 | 1 | 2011-07-01 | 1 | 
|  1 | 2011-07-02 13:45:12 | 1 | 2011-07-02 | 1 | 
|  1 | 2011-08-01 00:11:45 | 2 | 2011-08-01 | 1 | 
|  1 | 2011-08-05 17:14:34 | 3 | 2011-08-05 | 1 | 
|  1 | 2011-08-05 18:11:46 | 3 | 2011-08-05 | 1 | 
|  1 | 2011-08-06 20:22:12 | 3 | 2011-08-06 | 1 | 
|  2 | 2011-08-30 16:13:34 | 1 | 2011-08-30 | 2 | 
|  2 | 2011-08-31 16:13:41 | 1 | 2011-08-31 | 2 | 
+---------+---------------------+------+------------+----+ 

Sau đó, chúng tôi nhóm dữ liệu này được thiết lập bởi người sử dụng và sử dụng chức năng tổng hợp: 'COUNT (DISTINCT (DATE (dt))) '- đếm số ngày ' MAX (ngày) '- số lượt truy cập, đây là giá trị tối đa cho trường days từ truy vấn phụ của chúng tôi.

Đó là tất cả;)

+0

Có vẻ như khá phức tạp ... bạn có thể vui lòng cung cấp thêm một số chi tiết về mã của bạn không? Sẽ đánh giá cao! – linkyndy

+0

Tôi đã thêm một số chi tiết. – Devart

+0

Cảm ơn bạn đã biết chi tiết. Thật buồn là tôi không thể trả tiền thưởng cho hai câu trả lời. Tuy nhiên, tôi đã chọn câu trả lời khác vì truy vấn đơn giản hơn một chút. Tôi thực sự xin lỗi và tôi muốn cảm ơn bạn một lần nữa vì câu trả lời của bạn! – linkyndy

1

Như mẫu dữ liệu được cung cấp bởi Devart, khu vực nội "PreQuery" làm việc với các biến sql. Theo mặc định @LUser thành -1 (ID người dùng không tồn tại có thể xảy ra), kiểm tra IF() kiểm tra bất kỳ sự khác biệt nào giữa người dùng cuối và hiện tại. Ngay sau khi một người dùng mới, nó nhận được một giá trị 1 ... Ngoài ra, nếu ngày cuối cùng là hơn 1 ngày kể từ ngày đăng ký mới, nó nhận được một giá trị là 1. Sau đó, các cột tiếp theo đặt lại @LUser và @LDate cho giá trị của bản ghi đến vừa được kiểm tra đối với chu kỳ tiếp theo. Sau đó, truy vấn bên ngoài chỉ tổng hợp chúng và đếm chúng cho kết quả chính xác cuối cùng cho mỗi tập dữ liệu Devart của

User ID Distinct Visits Total Days 
1   3     9 
2   1     2 

select PreQuery.User_ID, 
     sum(PreQuery.NextVisit) as DistinctVisits, 
     count(*) as TotalDays 
    from 
     ( select v.user_id, 
       if(@LUser <> v.User_ID OR @LDate < (date(v.dt) - Interval 1 day), 1, 0) as NextVisit, 
       @LUser := v.user_id, 
       @LDate := date(v.dt) 
      from 
       Visits v, 
       (select @LUser := -1, @LDate := date(now())) AtVars 
      order by 
       v.user_id, 
       v.dt ) PreQuery 
    group by 
     PreQuery.User_ID 
+0

Cảm ơn bạn đã trả lời và làm rõ câu trả lời! – linkyndy

+0

Rất vui khi được trợ giúp ... nó có nhận được giải pháp chính xác mà bạn cần (do đó cũng bao gồm cả thông tin ID người dùng, để trợ giúp). – DRapp

+0

Nó đã làm, quá xấu chỉ có một câu trả lời có thể được chấp nhận và khen thưởng ... – linkyndy