Выберите одну строку за определенное время - PullRequest
3 голосов
/ 04 сентября 2011

У меня есть таблица, которая выглядит следующим образом:

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

Идентификаторы 3 и 4 не отображаются, поскольку не прошло 15 минут с момента последней записи для конкретного идентификатора пользователя.

Отображаются идентификаторы 1 и 5потому что прошло 15 минут для этого конкретного идентификатора пользователя. То же, что и для идентификаторов 2 и 6.

Как я могу это сделать?

Спасибо

Ответы [ 3 ]

1 голос
/ 04 сентября 2011

Попробуйте это:

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 - это столбец 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
0 голосов
/ 07 сентября 2011

Вы можете использовать для этого рекурсивный 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)
0 голосов
/ 04 сентября 2011

Как насчет этого, это довольно просто и дает вам нужный результат:

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 вместо этого для улучшения удобочитаемости и улучшения удобства обслуживания, также я улучшил его до выделенного, где вы также ограничитетаблица Dates по любому диапазону DateTime, который вы бы ограничивали основным запросом:

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

Также в качестве примечания по производительности, всякий раз, когда имеет дело с чем-то, что может оказаться рекурсивным, как это может быть (из другихответы), вы должны сразу подумать, если вы можете ограничитьn запрос по диапазону дат в целом, даже если это целый год или более длинный диапазон

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...