2011-09-04 70 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)