2011-08-17 57 views
8

我有一個數據庫表,用於保存城市中的每個用戶的簽入。我需要知道用戶在一個城市有多少天,然後,用戶對一個城市進行了多少次訪問(一次訪問包括城市中連續的幾天)。MySQL:按連續日期和計數組分組

所以,考慮我有如下表(簡化,只包含DATETIME秒 - 相同的用戶,市):

 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 

該用戶已被這個城市的天數將 (30.0601.0702.0701.0805.0806.08)。

我想這樣做使用SELECT COUNT(id) FROM table GROUP BY DATE(datetime)

然後,該用戶已經到這個城市的巡查次數,查詢應該返回(30.06-02.0701.0805.08 -06.08)。

問題是我不知道我該如何建立這個查詢。

任何幫助將不勝感激!

回答

10

您可以通過找到有沒有籤前一天簽發現每次訪問的第一天。

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 

該查詢有幾個重要部分。

首先,每個簽入都會加入前一天的任何簽入。但由於它是一個外連接,如果前一天沒有簽入,連接的右側將會有NULL結果。 WHERE過濾發生在聯接後,因此它只保留左側沒有右側的那些簽入。 LEFT OUTER JOIN/WHERE IS NULL真的很方便找到的東西不是

然後它計數不同簽入日期,以確保它不會重複計數,如果用戶在訪問的第一天多次檢查。 (當我發現可能的錯誤時,我實際上添加了編輯部分。)

編輯:我只是重新閱讀您提出的第一個問題的查詢。您的查詢會爲您提供給定日期的簽入數量,而不是日期數量。我想你想這樣的事情,而不是:

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

Devart數據集的最後正確的結果......我似乎無法完全理解你的建議?是否有可能給一些更多的細節?謝謝!關於第二個問題,我的問題是正確的,只要你不計算用戶和城市,正如我的問題所述。 – linkyndy

+0

對不起,我以爲,對於「多少天,用戶一直在一個城市」的結果應該像(USER_ID,COUNT_OF_DAYS)。 – Simon

+0

謝謝你的細節。經過幾次調整以適合我的實際數據庫表,您的查詢就像一個魅力。再次感謝你! – linkyndy

0

爲第一子任務:

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

我想你應該考慮改變數據庫結構。您可以將表訪問和visit_id添加到checkins表中。每次你想註冊新簽入時,你都要檢查一天後是否有簽入。如果是,那麼你從昨天的checkin中添加一個新的check_id和visit_id。如果沒有,那麼你添加新的訪問訪問和新的check_in與新的visit_id。

然後,你可以讓你的數據在一個查詢中有這樣的事情: SELECT COUNT(id) AS number_of_days, COUNT(DISTINCT visit_id) number_of_visits FROM checkin GROUP BY user, city

這不是很優化,但仍比目前的結構做什麼都好,它會工作。此外,如果結果可以單獨查詢,它將工作得非常快。

當然缺點,但你將需要更改數據庫結構,做一些更多的腳本和當前的數據轉換到新的結構(即你需要visit_id添加到當前數據)。

+0

謝謝你的答案,但我想堅持到我目前的數據庫結構,至少目前是這樣。此外,我需要插入時做一些進一步的操作,一天可以有多個檢查插件,所以它不是那麼簡單的「檢查是否有任何一天背籤」。這種數據操作也可以使用提供的數據庫結構在PHP中進行,但我一直在尋找一個查詢來完成這項工作,因爲它更加乾淨和方便。 – linkyndy

3

嘗試將此代碼應用到你的任務 -

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

說明:

要了解它是如何工作的,讓我們檢查子查詢,在這兒呢。

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; 

正如您所看到的,查詢返回所有行並對訪問次數執行排名。這是基於變量的已知排名方法,請注意,行由用戶和日期字段排序。這個查詢計算用戶訪問,並輸出下一個數據集,其中days列訪問的次數提供秩 -

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

然後我們組該數據由用戶設置和使用聚集函數: 「COUNT(DISTINCT(DATE( DT)))」 - 計算的天 數‘MAX(天)’ - 訪問次數,這是從我們的子查詢days場的最大值。

這是所有)

+0

它看起來很複雜...你能給我一些關於你的代碼的更多細節嗎?將不勝感激! – linkyndy

+0

我已添加一些細節。 – Devart

+0

謝謝你的細節。我不能給予兩個答案的賞金,這是非常可悲的。但是,我選擇了另一個答案,因爲查詢有點簡單。我非常抱歉,我想再次感謝你的回答! – linkyndy

1

如Devart提供的數據樣本,內部的「PreQuery」可與SQL變量。通過將@LUser默認爲-1(可能不存在的用戶ID),IF()測試檢查最後用戶和當前之間的任何差異。一旦一個新的用戶,它得到的值1 ......此外,如果最後日期是從辦理登機手續的新日期超過1天,它就會爲1的值。然後,後續列以重置@LUser和@LDate爲下一個週期剛剛測試的傳入記錄的值。然後,外部查詢只是總結起來,並計算它們每關於第一個方面的

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

謝謝您的回答和澄清吧! – linkyndy

+0

很樂意幫忙...沒有得到它,你所需要的精確解(因此包括用戶ID信息太多,幫助)。 – DRapp

+0

它沒有,太糟糕了答案只有一個可以被接受和獎勵... – linkyndy