1

我正在尝试在 SQL Server 2008/TSQL 中运行某种循环,但我不确定这应该是 aWHILE还是CURSOR两者。最终结果是我试图遍历用户登录列表,然后确定唯一用户,然后运行循环以确定用户在网站上停留 5 分钟所需的访问次数,按频道划分.

表:登录历史

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 到这个模式

我可以select像这样将唯一用户放入一个新表中:

SELECT UserID
INTO #Users
FROM LoginHistory
GROUP BY UserID

现在,我正在尝试开发的功能是遍历这些唯一的 UserID,按 DateTime 对登录进行排序,然后计算达到 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

** 好的 - 我在嘲笑自己结合多种不同语言/语法的方式,但这就是我正在考虑解决这个问题的方式**

想到一个好的方法来实现这一点?我的偏好是使用循环,这样我就可以继续将if/then逻辑写入代码。

4

2 回答 2

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

请注意,您的样本结果似乎有错误。 UserId3 在两次通话中达到 300 秒:180 + 120。您的示例显示了三个通话。

于 2013-02-13T04:30:13.497 回答
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
于 2013-02-12T21:40:22.007 回答