2013-02-12 3 views
1

Я пытаюсь запустить какой-то цикл в SQL Server 2008/TSQL, и я не уверен, должно ли это быть WHILE или CURSOR или и то, и другое. Конечным результатом является попытка просмотреть список пользовательских логинов, затем определить уникальных пользователей, а затем запустить цикл, чтобы определить, сколько посещений потребовалось для пользователя на сайте в течение 5 минут, вырванных каналом ,Запуск WHILE или CURSOR или оба в SQL Server 2008

Таблица: LoginHistory

UserID Channel DateTime   DurationInSeconds 
1  Website 1/1/2013 1:13PM 170 
2  Mobile 1/1/2013 2:10PM 60 
3  Website 1/1/2013 3:10PM 180 
4  Website 1/1/2013 3:20PM 280 
5  Website 1/1/2013 5:00PM 60 
1  Website 1/1/2013 5:05PM 500 
3  Website 1/1/2013 5:45PM 120 
1  Mobile 1/1/2013 6:00PM 30 
2  Mobile 1/1/2013 6:10PM 90 
5  Mobile 1/1/2013 7:30PM 400 
3  Website 1/1/2013 8:00PM 30 
1  Mobile 1/1/2013 9:30PM 200 

SQL Fiddle to this schema

Я select уникальных пользователей в новую таблицу следующим образом:

SELECT UserID 
INTO #Users 
FROM LoginHistory 
GROUP BY UserID 

Теперь функциональность я пытаюсь развивать это чтобы перебрать эти уникальные идентификаторы пользователя, заказать логины по дате времени, а затем подсчитать количество логинов, необходимых для перехода на 300 секунд.

Результирующий набор Я хотел бы надеяться попасть будет выглядеть примерно так:

UserID TotalLogins WebsiteLogins MobileLogins Loginsneededto5Min 
1  4   2    2    2 
2  2   2    0    0 
3  3   3    0    3 
4  1   1    0    0 
5  2   1    1    2 

Если бы я выполнять это на другом языке, я думаю, было бы что-то вроде этого: (А извинений, потому что это не полный, только там, где я думаю, что я собираюсь)

for (i in #Users): 
    TotalLogins = Count(*), 
    WebsiteLogins = Count(*) WHERE Channel = 'Website', 
    MobileLogins = Count(*) WHERE Channel = 'Mobile', 
    for (i in LoginHistory): 
     if Duration < 300: 
     count(NumLogins) + 1 

** Ok - Я смеюсь над собой, как я комбинированную несколько различных языков/синтаксисом, но это, как я думаю о решении этого **

Мысли о хорошем способе выполнить это? Мое предпочтение заключается в использовании цикла, поэтому я могу продолжать писать логику if/then в код.

+1

Несмотря на то, что вы предпочитаете цикл, было бы приемлемым решение на основе набора? – HABO

+0

@HABO Я открыт для других решений, но я знаю, что в конечном итоге мне понадобится другая логика, например, «какая сессия имела самую длинную продолжительность» или другие подобные вычисления. Благодарю. – mikebmassey

+0

Похоже, что рекурсивный запрос будет делать то, что вы хотите. Также называются CTE, они объясняются здесь [http://msdn.microsoft.com/en-us/library/ms186243 (v = sql.105) .aspx). Якорь будет уникальными парами UserId/Channel, и рекурсия добавит логины обратно во времени до достижения желаемой продолжительности или другого условия завершения, например. максимальный возврат 3 месяца. – HABO

ответ

0

Хорошо, это одно из тех случаев, когда CURSOR, вероятно, превосходит решение на основе набора. К сожалению, я не очень хорошо с курсорами, так что я могу дать вам набор базового решения для вас попробовать:

;WITH CTE AS 
(
    SELECT *, ROW_NUMBER() OVER(PARTITION BY UserID ORDER BY [DateTime]) RN 
    FROM UserLogins 
), CTE2 AS 
(
    SELECT *, 1 RecursionLevel 
    FROM CTE 
    WHERE RN = 1 
    UNION ALL 
    SELECT B.UserID, B.Channel, B.[DateTime], 
      A.DurationInSeconds+B.DurationInSeconds, 
      B.RN, RecursionLevel+1 
    FROM CTE2 A 
    INNER JOIN CTE B 
     ON A.UserID = B.UserID AND A.RN = B.RN - 1 
) 
SELECT A.UserID, 
     COUNT(*) TotalLogins, 
     SUM(CASE WHEN Channel = 'Website' THEN 1 ELSE 0 END) WebsiteLogins, 
     SUM(CASE WHEN Channel = 'Mobile' THEN 1 ELSE 0 END) MobileLogins, 
     ISNULL(MIN(RecursionLevel),0) LoginsNeedeto5Min 
FROM UserLogins A 
LEFT JOIN (SELECT UserID, MIN(RecursionLevel) RecursionLevel 
      FROM CTE2 
      WHERE DurationInSeconds > 300 
      GROUP BY UserID) B 
    ON A.UserID = B.UserID 
GROUP BY A.UserID 
0

несколько иной кусочная подход. Небольшое различие заключается в том, что рекурсивная часть заканчивается, когда она достигает 300 секунд для каждого пользователя, а не суммирует все доступные логины.

Индекс на UserId/StartTime должен улучшить производительность при использовании более крупных наборов данных.

declare @Logins as Table (UserId Int, Channel VarChar(10), StartTime DateTime, DurationInSeconds Int) 
insert into @Logins (UserId, Channel, StartTime, DurationInSeconds) values 
    (1, 'Website', '1/1/2013 1:13PM', 170), 
    (2, 'Mobile', '1/1/2013 2:10PM', 60), 
    (3, 'Website', '1/1/2013 3:10PM', 180), 
    (4, 'Website', '1/1/2013 3:20PM', 280), 
    (5, 'Website', '1/1/2013 5:00PM', 60), 
    (1, 'Website', '1/1/2013 5:05PM', 500), 
    (3, 'Website', '1/1/2013 5:45PM', 120), 
    (1, 'Mobile', '1/1/2013 6:00PM', 30), 
    (2, 'Mobile', '1/1/2013 6:10PM', 90), 
    (5, 'Mobile', '1/1/2013 7:30PM', 400), 
    (3, 'Website', '1/1/2013 8:00PM', 30), 
    (1, 'Mobile', '1/1/2013 9:30PM', 200) 

select * from @Logins 

; with MostRecentLogins as (
    -- Logins with flags for channel and sequenced by StartTime (ascending) for each UserId . 
    select UserId, Channel, StartTime, DurationInSeconds, 
    case when Channel = 'Website' then 1 else 0 end as WebsiteLogin, 
    case when Channel = 'Mobile' then 1 else 0 end as MobileLogin, 
    Row_Number() over (partition by UserId order by StartTime) as Seq 
    from @Logins), 
    CumulativeDuration as (
    -- Start with the first login for each UserId . 
    select UserId, Seq, DurationInSeconds as CumulativeDurationInSeconds 
    from MostRecentLogins 
    where Seq = 1 
    union all 
    -- Accumulate additional logins for each UserId until the running total exceeds 300 or they run out of logins. 
    select CD.UserId, MRL.Seq, CD.CumulativeDurationInSeconds + MRL.DurationInSeconds 
    from CumulativeDuration as CD inner join 
     MostRecentLogins as MRL on MRL.UserId = CD.UserId and MRL.Seq = CD.Seq + 1 and CD.CumulativeDurationInSeconds < 300) 
    -- Display the summary. 
    select UserId, Sum(WebsiteLogin + MobileLogin) as TotalLogins, 
    Sum(WebsiteLogin) as WebsiteLogins, Sum(MobileLogin) as MobileLogins, 
    (select Max(Seq) from CumulativeDuration where UserId = LT3.UserId and CumulativeDurationInSeconds >= 300) as LoginsNeededTo5Min 
    from MostRecentLogins as LT3 
    group by UserId 
    order by UserId 

Обратите внимание, что результаты вашего образца, похоже, имеют ошибку. UserId 3 достигает 300 секунд в двух вызовах: 180 + 120. В вашем примере показаны три вызова.

Смежные вопросы