2011-09-04 85 views
3

我有一個表,看起來像這樣:選擇每個具體時間一行

ID UserID DateTime    TypeID 

1  1  1/1/2010 10:00:00  1 
2  2  1/1/2010 10:01:50  1 
3  1  1/1/2010 10:02:50  1 
4  1  1/1/2010 10:03:50  1 
5  1  1/1/2010 11:00:00  1 
6  2  1/1/2010 11:00:50  1 

我需要查詢所有用戶,他們的TYPEID是1,但必須每15分鐘

對於只有一行例如,結果應該是:

1  1  1/1/2010 10:00:00  1 
2  2  1/1/2010 10:01:50  1 
5  1  1/1/2010 11:00:00  1 
6  2  1/1/2010 11:00:50  1 

ID的3 & 4中未示出,因爲15分鐘沒有由於用於特定用戶ID的最後一個記錄通過。

ID號爲1 & 5所示原因在於第15分鐘以上已經通過了這個特定的用戶ID 同爲ID的2 & 6.

我該怎麼辦呢?

感謝

+1

爲什麼第二行輸出有TypeID 2? –

+0

oopsss .. fixed :) – Shay

+0

如果每分鐘發生一次事件持續20分鐘,您希望做什麼?你應該看到第一個還是第二個記錄,一個是第一分鐘,一個是第16分鐘? – Seph

回答

1

試試這個:

select * from 
(
     select ID, UserID, 
     Max(DateTime) as UpperBound, 
     Min(DateTime) as LowerBound, 
     TypeID 
     from the_table 
     where TypeID=1 
     group by ID,UserID,TypeID 
) t 
where datediff(mi,LowerBound,UpperBound)>=15 

編輯: SINCE我上面的企圖是錯的,我使用SQL表值函數不需要遞歸增加一個方法,因爲可以理解,這是一個大問題。

第1步:創建一個表類型如下(LoginDate是吉文的例子中,DateTime列 - 日期時間命名衝突與SQL數據類型,我認爲這是明智的,避免這些衝突)

CREATE TYPE [dbo].[TVP] AS TABLE(
    [ID] [int] NOT NULL, 
    [UserID] [int] NOT NULL, 
    [LoginDate] [datetime] NOT NULL, 
    [TypeID] [int] NOT NULL 
) 
GO 

步驟2:創建以下功能:

CREATE FUNCTION [dbo].[fnGetLoginFreq] 
(
    -- notice: TVP is the type (declared above) 
    @TVP TVP readonly 
) 
RETURNS 
@Table_Var TABLE 
(
    -- This will be our result set 
    ID int, 
    UserId int, 
    LoginTime datetime, 
    TypeID int, 
    RowNumber int 
) 
AS 
BEGIN 
    --We will insert records in this table as we go through the rows in the 
    --table passed in as parameter and decide that we should add an entry because 
    --15' had elapsed between logins 
    DECLARE @temp table 
    (
     ID int, 
     UserId int, 
     LoginTime datetime, 
     TypeID int 
    ) 
    -- seems silly, but is not because we need to add a row_number column to help 
    -- in our iteration and table-valued paramters cannot be modified inside the function 
    insert into @Table_var 
    select ID,UserID,Logindate,TypeID,row_number() OVER(ORDER BY UserID,LoginDate) AS [RowNumber] 
    from @TVP order by UserID asc,LoginDate desc 

    declare @Index int,@End int,@CurrentLoginTime datetime, @NextLoginTime datetime, @CurrentUserID int , @NextUserID int 

    select @Index=1,@End=count(*) from @Table_var 

    while(@Index<[email protected]) 
    begin   
      select @CurrentLoginTime=LoginTime,@CurrentUserID=UserID from @Table_var where [email protected] 
      select @NextLoginTime=LoginTime,@NextUserID=UserID from @Table_var where RowNumber=(@Index+1) 

      if(@[email protected]) 
      begin 
       if(abs(DateDiff(mi,@CurrentLoginTime,@NextLoginTime))>=15) 
       begin 
        insert into @temp 
        select ID,UserID,LoginTime,TypeID 
        from @Table_var 
        where [email protected] 
       end  
      END 
      else 
      bEGIN 
        insert into @temp 
        select ID,UserID,LoginTime,TypeID 
        from @Table_var 
        where [email protected] and [email protected] 
      END 

      if(@[email protected])--last element? 
      begin 
       insert into @temp 
       select ID,UserID,LoginTime,TypeID 
       from @Table_var 
       where [email protected] and not 
       abs((select datediff(mi,@CurrentLoginTime,max(LoginTime)) from @temp where [email protected]))<=14 
      end 

      select @[email protected]+1 
    end 

    delete from @Table_var 

    insert into @Table_var 
    select ID, UserID ,LoginTime ,TypeID ,row_number() OVER(ORDER BY UserID,LoginTime) AS 'RowNumber' 
    from @temp 

    return 

END 

步驟3:給它旋

declare @TVP TVP 

INSERT INTO @TVP 
select ID,UserId,[DateType],TypeID from Shays_table where TypeID=1 --AND any other date restriction you want to add 

select * from fnGetLoginFreq(@TVP) order by LoginTime asc 

我的測試,返回此:

ID UserId LoginTime    TypeID RowNumber 
2 2  2010-01-01 10:01:50.000 1  3 
4 1  2010-01-01 10:03:50.000 1  1 
5 1  2010-01-01 11:00:00.000 1  2 
6 2  2010-01-01 11:00:50.000 1  4 
+0

否,它不起作用。它給了我0條記錄。即使我做> = 1。 – Shay

+0

@Shay:我剛剛意識到你想根據你的數據樣本輸出兩次UserID 1。您將無法通過簡單的查詢來完成此操作。您必須通過UserID和[DateTime]進行排序並使用2個指針進行循環;一個指向當前行,另一個指向下一個。如果下一行的UserID與當前行相同,並且當前行的[DateTime]的TimeDiff和下一行的[DateTime]大於等於15,則將當前行插入臨時表中,並在下一行重複之後繼續行腳步。 – Icarus

+0

@Shay:完成第一次迭代後,您需要重複上述所有步驟,直到最終得到的臨時表不包含具有相同UserID的兩個連續行。那將是你的停止條件。如果你想在SQL中這樣做,你可以編寫一個表值函數,它接收一個表作爲參數並遞歸地調用它(你可以在SQL中最多有32個遞歸函數調用,但可以調整)。當我達到之前評論中提到的停止條件時,您將從該功能返回。祝你好運。 – Icarus

0

這個怎麼樣,這是相當簡單的,給你你需要的結果:

SELECT ID, UserID, [DateTime], TypeID 
FROM Users 
WHERE Users.TypeID = 1 
    AND NOT EXISTS (
    SELECT TOP 1 1 
    FROM Users AS U2 
    WHERE U2.ID <> Users.ID 
     AND U2.UserID = Users.UserID 
     AND U2.[DateTime] BETWEEN DATEADD(MI, -15, Users.[DateTime]) AND Users.[DateTime] 
     AND U2.TypeID = 1) 

NOT EXISTS限制,只顯示記錄的有前內15分鐘沒有記錄他們,所以你會看到第一個記錄在一個塊,而不是每15分鐘一個。

編輯:既然你想看到一個每15分鐘這應該離不開使用遞歸:

SELECT Users.ID, Users.UserID, Users.[DateTime], Users.TypeID 
FROM 
    (
    SELECT MIN(ID) AS ID, UserID, 
     DATEADD(minute, DATEDIFF(minute,0,[DateTime])/15 * 15, 0) AS [DateTime] 
    FROM Users 
    GROUP BY UserID, DATEADD(minute, DATEDIFF(minute,0,[DateTime])/15 * 15, 0) 
) AS Dates 
    INNER JOIN Users AS Users ON Users.ID = Dates.ID 
WHERE Users.TypeID = 1 
    AND NOT EXISTS (
    SELECT TOP 1 1 
    FROM 
     (
     SELECT MIN(ID) AS ID, UserID, 
      DATEADD(minute, DATEDIFF(minute,0,[DateTime])/15 * 15, 0) AS [DateTime] 
     FROM Users 
     GROUP BY UserID, DATEADD(minute, DATEDIFF(minute,0,[DateTime])/15 * 15, 0) 
    ) AS Dates2 
     INNER JOIN Users AS U2 ON U2.ID = Dates2.ID 
    WHERE U2.ID <> Users.ID 
     AND U2.UserID = Users.UserID 
     AND U2.[DateTime] BETWEEN DATEADD(MI, -15, Users.[DateTime]) AND Users.[DateTime] 
     AND U2.TypeID = 1 
) 
ORDER BY Users.DateTime 

如果這不起作用,請張貼更多的樣本數據,這樣我可以看到什麼是缺少的。

Edit2與上面直接相同,但現在只是使用CTE來改善可讀性並有助於提高可維護性,同時我還改進了它,以突出顯示在哪些日期時間範圍內,您將限制爲主要查詢:

WITH Dates(ID, UserID, [DateTime]) 
AS 
(
    SELECT MIN(ID) AS ID, UserID, 
    DATEADD(minute, DATEDIFF(minute,0,[DateTime])/15 * 15, 0) AS [DateTime] 
    FROM Users 
    WHERE Users.TypeID = 1 
    --AND Users.[DateTime] BETWEEN @StartDateTime AND @EndDateTime 
    GROUP BY UserID, DATEADD(minute, DATEDIFF(minute,0,[DateTime])/15 * 15, 0) 
) 

SELECT Users.ID, Users.UserID, Users.[DateTime], Users.TypeID 
FROM Dates 
    INNER JOIN Users ON Users.ID = Dates.ID 
WHERE Users.TypeID = 1 
    --AND Users.[DateTime] BETWEEN @StartDateTime AND @EndDateTime 
    AND NOT EXISTS (
    SELECT TOP 1 1 
    FROM Dates AS Dates2 
     INNER JOIN Users AS U2 ON U2.ID = Dates2.ID 
    WHERE U2.ID <> Users.ID 
     AND U2.UserID = Users.UserID 
     AND U2.[DateTime] BETWEEN DATEADD(MI, -15, Users.[DateTime]) AND Users.[DateTime] 
     AND U2.TypeID = 1 
) 
ORDER BY Users.DateTime 

另外,作爲一個性能注意,每當處理一些可能最終會被這樣的遞歸潛在可能(從其他答案),你應該馬上進行考慮,如果你能限制主查詢在一般日期範圍內即使是全年或更長的範圍

+0

這不起作用。如果時間跨度是每分鐘或者說是'10:00','10:14','10:28','10:42'等,那麼間隙小於15分鐘的表中存在一行會阻止除了成爲輸出的第一行之外。 –

+0

@ Seph:有趣......但我確實需要「每15分鐘一次......」 – Shay

+0

正如我先前指出的那樣,如果需要每15分鐘顯示一個或只顯示那些「15 min已經過去自特定用戶ID的最後一個記錄。「如OP所述。我現在更新了我的答案,以適應省略具有「不到15分鐘差距」的記錄的額外要求。 – Seph

0

您可以使用遞歸CTE,但如果結果集非常大,我也會評估一個遊標,因爲它可能更有效。

我在回答中忽略了ID列。如果你真的需要它,可以添加它。它只是使遞歸CTE的錨定部分更加笨拙。

DECLARE @T TABLE 
(
ID INT PRIMARY KEY, 
UserID INT, 
[DateTime] DateTime, 
TypeID INT 
) 
INSERT INTO @T 
SELECT 1,1,'20100101 10:00:00', 1 union all 
SELECT 2,2,'20100101 10:01:50', 1 union all 
SELECT 3,1,'20100101 10:02:50', 1 union all 
SELECT 4,1,'20100101 10:03:50', 1 union all 
SELECT 5,1,'20100101 11:00:00', 1 union all 
SELECT 6,2,'20100101 11:00:50', 1; 


WITH RecursiveCTE 
    AS (SELECT UserID, 
       MIN([DateTime]) As [DateTime], 
       1    AS TypeID 
     FROM @T 
     WHERE TypeID = 1 
     GROUP BY UserID 
     UNION ALL 
     SELECT UserID, 
       [DateTime], 
       TypeID 
     FROM (
       --Can't use TOP directly 
       SELECT T.*, 
         rn = ROW_NUMBER() OVER (PARTITION BY T.UserID ORDER BY 
          T.[DateTime]) 
       FROM @T T 
         JOIN RecursiveCTE R 
          ON R.UserID = T.UserID 
          AND T.[DateTime] >= 
           DATEADD(MINUTE, 15, R.[DateTime])) R 
     WHERE R.rn = 1)