如何在没有游标的情况下分析SQL数据中的记录之间的时间段?

时间:2009-08-14 07:34:15

标签: sql

根本问题:我有一个已经运行了几个月的应用程序。用户一直在报告它随着时间的推移一直在减速(因此在五月它比现在更快)。我需要得到一些证据来支持或驳斥这一主张。我对精确数字不感兴趣(所以我不需要知道登录花了10秒钟),我对趋势很感兴趣 - 过去需要x秒的东西现在需要大约几秒钟。< / p>

我拥有的数据是一个审计表,每次用户执行任何活动时都会存储一行 - 它包括主键,用户ID,日期时间戳和活动代码:

create table AuditData (
    AuditRecordID int identity(1,1) not null, 
    DateTimeStamp datetime not null,
    DateOnly datetime null,
    UserID nvarchar(10) not null,
    ActivityCode int not null)

(注意:DateOnly(datetime)是DateTimeStamp,其时间被剥离以使分组更容易进行日常分析 - 它实际上是重复数据以使查询更快)。

同样为了方便起见,您可以假设ID按照日期时间顺序分配,即1始终在2之前,始终在3之前 - 如果不是这样我可以这样做)。

ActivityCode是一个标识发生的活动的整数,例如1可能是用户登录,2可能是用户数据返回,3可能是返回的搜索结果等等。

那些喜欢那种东西的人的样本数据......:

1, 01/01/2009 12:39, 01/01/2009, P123, 1
2, 01/01/2009 12:40, 01/01/2009, P123, 2
3, 01/01/2009 12:47, 01/01/2009, P123, 3
4, 01/01/2009 13:01, 01/01/2009, P123, 3

登录后立即返回用户数据(活动代码2)(活动代码1),因此这可以用作登录所用时间的粗略基准(正如我所说,我对趋势感兴趣所以只要我在May测量同样的东西和7月一样,如果这不是整个登录过程并不重要 - 它需要足够的时间来提供一个粗略的想法。

(注意:用户数据也可以在其他情况下返回,因此不是一对一的映射。)

所以我要做的是选择登录之间的平均时间(比如ActivityID 1)和当天该用户的第一个实例返回的用户数据(比如ActivityID) 2)。

我可以通过使用游标遍历表,获取每个登录实例然后执行select操作来获取当天为该用户返回的最小用户数据,但这显然不是最佳的,并且是地狱慢了。

我的问题是(最后) - 是否有一种“正确的”SQL方法,使用自连接或类似方法,而不使用游标或类似的程序方法?我可以创建视图和任何内容,它不一定是一个选择。

我可以一起破解某些东西,但我想进行分析,我正在做一个标准的产品功能,所以希望它是正确的。

4 个答案:

答案 0 :(得分:1)

SELECT TheDay, AVG(TimeTaken) AvgTimeTaken
FROM (  
SELECT 
    CONVERT(DATE, logins.DateTimeStamp) TheDay
    , DATEDIFF(SS, logins.DateTimeStamp, 
                (SELECT TOP 1 DateTimeStamp 
                 FROM AuditData userinfo 
                 WHERE UserID=logins.UserID 
                    and userinfo.ActivityCode=2 
                    and userinfo.DateTimeStamp > logins.DateTimeStamp )
                )TimeTaken
FROM AuditData logins
WHERE 
    logins.ActivityCode = 1
) LogInTimes
GROUP BY TheDay

但在现实世界中,这可能会慢得多。

答案 1 :(得分:1)

在Oracle中,由于分析功能,这将是一个很好的结果。在这种情况下,LAG()可以轻松找到匹配的活动代码对1和2,也可以计算趋势。正如你所看到的那样,事情在1月2日变得更糟,并在3日有所改善(我在几秒钟而不是几分钟内工作)。

SQL> select DateOnly
  2         , elapsed_time
  3         , elapsed_time - lag (elapsed_time) over (order by DateOnly) as trend
  4  from
  5      (
  6      select DateOnly
  7             , avg(databack_time - prior_login_time) as elapsed_time
  8      from
  9          ( select DateOnly
 10                  , databack_time
 11                  , ActivityCode
 12                  , lag(login_time) over (order by DateOnly,UserID, AuditRecordID, ActivityCode) as prior_login_time
 13            from
 14              (
 15                  select a1.AuditRecordID
 16                         , a1.DateOnly
 17                         , a1.UserID
 18                         , a1.ActivityCode
 19                         , to_number(to_char(a1.DateTimeStamp, 'SSSSS')) as login_time
 20                         , 0 as databack_time
 21                  from   AuditData a1
 22                  where a1.ActivityCode = 1
 23                  union all
 24                  select a2.AuditRecordID
 25                         , a2.DateOnly
 26                         , a2.UserID
 27                         , a2.ActivityCode
 28                         , 0 as login_time
 29                         , to_number(to_char(a2.DateTimeStamp, 'SSSSS')) as databack_time
 30                  from   AuditData a2
 31                  where a2.ActivityCode = 2
 32                  )
 33              )
 34      where ActivityCode = 2
 35      group by  DateOnly
 36  )
 37  /

DATEONLY  ELAPSED_TIME      TREND
--------- ------------ ----------
01-JAN-09          120
02-JAN-09          600        480
03-JAN-09          150       -450

SQL>

就像我在评论中所说,我猜你在MSSQL工作。我不知道该产品是否具有任何等效的LAG()。

答案 2 :(得分:1)

如果假设是:

  1. 用户将按照规定的顺序执行各种任务,
  2. 任何两个活动之间的差异反映了这两个活动中的第一个活动执行所需的时间,
  3. 那么为什么不创建一个包含两个时间戳的表,第一列包含活动开始时间,第二列包含下一个活动开始时间。因此,这两者之间的差异将始终是第一次活动的总时间。因此,对于注销活动,第二列只有NULL。

    因此,对于每个活动(除了登录和退出),它会有点奇怪和有趣,时间戳将记录在两个不同的行中 - 一次用于最后一个活动(作为时间“完成”) )并再次在新的一行(随着时间的推移)。你最终会得到一个雅各布的阶梯,但找到你想要的数据会更加简单。

    事实上,要真正古怪,你可以让每一行都有用户启动活动A和活动代码的时间,时间开始活动B和时间戳(如上所述,它被放下再次为下一行)。这样,每一行都会告诉您任何两个活动的确切时间差异。

    否则,您会遇到类似

    的查询
    SELECT TIME_IN_SEC(row2-timestamp) - TIME_IN_SEC(row1-timestamp)
    

    这将是非常慢的,正如你已经建议的那样。通过吞下冗余,您最终只是查询两列之间的差异。您可能不太需要知道用户信息,因为您知道任何行都显示两个活动代码,因此您可以查询任何给定日期所有用户的平均值并将其与第二天进行比较(除非你试图找出哪些用户也遇到了这个问题。)

答案 3 :(得分:1)

这是查找速度越快的查询,在一行中您将在datetime值之前有当前行和行,之后您可以使用DATEDIFF(datepart,startdate,enddate)。我使用@DammyVariable和DamyField,因为我记得如果不是第一个@ variable =更新语句中的字段就会出现问题。

SELECT *, Cast(NULL AS DateTime) LastRowDateTime, Cast(NULL As INT) DamyField INTO #T FROM AuditData 
GO
CREATE CLUSTERED INDEX IX_T ON #T (AuditRecordID)
GO
DECLARE @LastRowDateTime DateTime
DECLARE @DammyVariable INT

SET @LastRowDateTime = NULL 
SET @DammyVariable = 1

UPDATE #T SET 
  @DammyVariable = DammyField = @DammyVariable
, LastRowDateTime = @LastRowDateTime 
    , @LastRowDateTime = DateTimeStamp 
option (maxdop 1)