--==========================================
需求:有一個用戶登陸日志表,記錄用戶每次登陸時間,然后想查找用戶按天連續登陸的情況,找出每次連續登陸的最早時間和最后時間以及連續登陸天數。
--===========================================
由於長久未寫此類SQL,有點手生,本着走一步算一步的精神,慢慢來。
首先查看日志表
SELECT [Uid] ,[loginDate] FROM [dbo].[Member_LoginLog] WHERE [UID]=268

由於按天計算連續登陸,表中時間精確到毫秒,很難肉眼看出數據是否連續,於是考慮轉換數據
而又由於我們只關心最早登陸時間和最后登陸時間,因此我們可以先按照天來統計用戶最早登陸時間和最后登陸時間,並將時間轉換成對應天數
--============================================== --統計出用戶每天最早登陸時間和最后登陸時間 SELECT T1.[UID] ,DATEDIFF(DAY,'2014-01-01',LoginDate) AS DiffDays ,MAX(LoginDate) AS MaxLoginDate ,MIN(LoginDate) AS MinLoginDate INTO [dbo].[Member_LoginLog_Status1] FROM [dbo].[Member_LoginLog] T1 GROUP BY T1.[UID],DATEDIFF(DAY,'2014-01-01',LoginDate) --====================================== --查看效果 SELECT [UID] ,[DiffDays] ,[MaxLoginDate] ,[MinLoginDate] FROM [dbo].[Member_LoginLog_Status1] WHERE UID=268

從上圖很容易看出第二天沒連續登陸,是不是很容易看啊
接下來就是查找聯系的天數了,如果我們按照UID分組,然后對DiffDays來排序求出排名來,依據DiffDays的增長量和RID量便可以判斷出天數是否連續
SELECT ROW_NUMBER()OVER(PARTITION BY UID ORDER BY [DiffDays] ASC) AS RID, T1.* FROM [dbo].[Member_LoginLog_Status1] T1 WHERE [UID]=268

這樣我們便可以使用表的自連接來查找連續的登錄,由於需要按照用戶和天數來算出排名,因此我們可以先建立索引
CREATE CLUSTERED INDEX CIX_UID_Days ON [dbo].[Member_LoginLog_Status1] ( [UID],[DiffDays] )
然后再求連續區間:
--========================================== --查找連續的登錄 ;WITH Tem AS( SELECT ROW_NUMBER()OVER(PARTITION BY UID ORDER BY [DiffDays] ASC) AS RID, T1.* FROM [dbo].[Member_LoginLog_Status1] T1 ) ,Tem1 AS( SELECT ROW_NUMBER()OVER( PARTITION BY T1.[UID],T1.[DiffDays] ORDER BY T2.[diffdays]-T1.[diffdays] DESC) AS RID, T1.[UID], T1.MinLoginDate, T2.MaxLoginDate, T1.[diffdays] AS MinDiffDays, T2.[diffdays] AS MAXDiffDays FROM Tem AS T1 INNER JOIN Tem AS T2 ON T1.UID=T2.UID AND T1.[diffdays]<=T2.[diffdays] AND T2.[diffdays]-T1.[diffdays]= T2.RID-T1.RID ) SELECT [UID], MinLoginDate, MaxLoginDate, MinDiffDays, MAXDiffDays INTO [dbo].[Member_LoginLog_Status2] FROM Tem1 AS T1 WHERE T1.RID=1 --========================================= --檢查結果 SELECT [UID] ,[MinLoginDate] ,[MaxLoginDate] ,[MinDiffDays] ,[MAXDiffDays] FROM [dbo].[Member_LoginLog_Status2] WHERE [UID]=268

找出連續的區間后,我們會發現有很多區間不是最大連續區間,如第5天到第17天連續,但是比之更大的區間還有第3天到第17天,對於這種問題,解決辦法就是依據maxDiffDays分組,求出最小的minDiffDays
由於此時要按照用戶和maxDiffDays分組,然后按照MinDiffDays排序求最小值,因此先建立索引
CREATE CLUSTERED INDEX CIX_UID_MAXDiffDays ON [AccMain_101].[dbo].[Member_LoginLog_Status2] ([UID],MAXDiffDays,MinDiffDays ASC)
然后再查詢:
--==================================== --求出最大連續區間 ;WITH CTE1 AS( SELECT ROW_NUMBER()OVER(PARTITION BY [UID],MAXDiffDays ORDER BY MinDiffDays ASC) AS RID, [UID], MinLoginDate, MaxLoginDate, MinDiffDays, MAXDiffDays FROM [AccMain_101].[dbo].[Member_LoginLog_Status2] AS T1 ) INSERT INTO [dbo].[Member_LoginLog_Status3] ([Uid] ,[firstLoginDate] ,[lastLoginDate] ,[loginNumber]) SELECT [UID], MinLoginDate, MaxLoginDate, T1.MAXDiffDays-MinDiffDays AS ContinueDays FROM CTE1 T1 WHERE T1.RID=1 --================================== --查看結果 SELECT [Uid] ,[firstLoginDate] ,[lastLoginDate] ,[loginNumber] FROM [dbo].[Member_LoginLog_Status3] WHERE [UID]=268
查詢結果:

結果正是我們想要的,因此打完收工,回家吃飯。
--===============================================
總結:其實查找連續或查找孤島這類原理,都是利用自連接然后看增長是否連續,多折騰幾遍就好。
--===============================================
在wwwwgou的回復中,指出一條更快捷的計算方式,同樣使用排名來計算,但不使用關聯,而是計算排名與登陸天數的差值,如果登陸天數連續增長,則排名也連續增長,兩者的差值保持不變;如果登陸天數不連續,則登陸天數增長的值就會比排名增長的值高,這時兩者的差值就會變大。
如下圖:

隨着天數不連續的次數增加,[天數-排名]的值會不斷增大,因此可以使用[天數-排名]來分組,便可以定位到連續區間。
PS: 不會出現兩個不同連續區間的[天數-排名]值一樣的情況
查找代碼:
--======================================== --感謝wwwwgou提供, --此代碼已略做修改 SELECT [Uid], mindt = MIN(mindt), maxdt = MAX(maxdt), logdays = COUNT(*) FROM ( SELECT [Uid], RowNo = ROW_NUMBER() OVER(PARTITION BY [Uid] ORDER BY DATEDIFF(DAY,'2014-01-01', loginDate)), DiffDay = DATEDIFF(DAY,'2014-01-01', loginDate), mindt = MIN(loginDate), maxdt = MAX(loginDate) FROM dbo.Member_LoginLog GROUP BY [Uid], DATEDIFF(DAY,'2014-01-01', loginDate) ) T GROUP BY [Uid], [RowNo] - DiffDay ORDER BY [Uid], minDt
對wwwwgou筒子再次表示嬸嬸地感謝。
--===============================================
請原諒我蒼白的講解,讓您們只能看代碼。
妹子騷猴就上,不要着急。

