每个特定时间选择一行

时间:2011-09-04 06:31:06

标签: sql sql-server linq linq-to-sql

我有一个看起来像这样的表:

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,因为自特定用户ID的最后一条记录以来尚未通过15分钟。

ID 1&显示了5,因为此特定用户ID已经过了15分钟 与ID 2和& 6。

我该怎么做?

由于

3 个答案:

答案 0 :(得分: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

编辑:由于我的上述尝试错误,我正在使用不需要递归的Sql表值函数添加另一种方法,因为可以理解,这是一个很大的问题。

步骤1:创建一个表类型如下(LoginDate是Shay示例中的DateTime列 - 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<=@End)
    begin        
            select @CurrentLoginTime=LoginTime,@CurrentUserID=UserID from @Table_var where RowNumber=@Index
            select @NextLoginTime=LoginTime,@NextUserID=UserID from @Table_var where RowNumber=(@Index+1)

            if(@CurrentUserID=@NextUserID)
            begin
                if( abs(DateDiff(mi,@CurrentLoginTime,@NextLoginTime))>=15)
                begin   
                    insert into @temp
                    select ID,UserID,LoginTime,TypeID
                    from @Table_var
                    where RowNumber=@Index
                end     
            END
            else 
            bEGIN
                    insert into @temp
                    select ID,UserID,LoginTime,TypeID
                    from @Table_var
                    where RowNumber=@Index and UserID=@CurrentUserID 
            END

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

            select @Index=@Index+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

答案 1 :(得分: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来提高可读性并帮助提高可维护性,我也将其改进为高亮显示,您还可以通过限制主查询的任何DateTime范围来限制Dates表:

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

同样作为一个性能说明,每当处理可能最终会递归的事情(可能是其他答案)时,您应该立即考虑是否能够将主查询限制在日期范围内一般即使它是一整年或更长的范围

答案 2 :(得分: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)