【问题标题】:Complex / Algorithmic SQL Query复杂/算法 SQL 查询
【发布时间】:2011-01-04 21:04:32
【问题描述】:

我想知道是否有可能实现一个 SQL 查询,它会像一种算法一样根据下表为我计算某个数字:

这是初始查询,

SELECT Activity, TimeOfAction, Requestor
FROM EventLog
WHERE Requestor = 0
ORDER BY Requestor, TimeOfAction;

以及返回的数据样本,

Login   2010-05-28 15:52:50.590 0

Login   2010-05-28 15:52:50.873 0

Logout  2010-05-28 15:52:50.890 0

Logout  2010-05-28 16:22:57.983 0

Login   2010-05-29 11:29:36.967 0

Logout  2010-05-29 11:29:37.640 0

如您所见,此数据集中有重复的登录和注销。当有重复时,我需要通过第一次登录和最后一次注销来计算会话的长度。因此,给出上述数据的第一个会话将来自,

5-28 15:52:50.590 to 5-28 16:22:57.983

算法大致,

1) 按用户名排序登录/注销列表,然后按操作时间排序

2) 如果条目是登录,则搜索下一个登出,紧接着是一个登录(以确认它是所有重复项中的最后一个登出)

3) 使用第一次登录和最后一次注销创建一个新会话(长度为注销时间-登录时间)

4) 重复

目前我只是在代码中实现它,但想知道它是否可以在 SQL 中实现(我对 SQL 不太熟悉)。

【问题讨论】:

    标签: c# sql-server sql


    【解决方案1】:

    当然……试试这样的。

    select e1.Requestor, 
           e1.TimeOfAction as LoginTime, 
           (select min(ActivityTime)
            from EventLog where TimeOfAction > e1.TimeOfAction 
            and Activity = 'Logout') as LogoutTime
    from EventLog e1
    where e1.ActivityType = 'Login'
    order by Requestor, LoginTime
    

    第二个解决方案...看看这是否更适合你。

    select requestor,
        (select min(activitytime)
         from eventlog 
         where activitytime < e.activitytime
         and activity = 'Login' and e.activity = 'Logout') as LoginTime, 
        (select max(activitytime)
         from eventlog 
         where activitytime > e.activitytime
         and activity = 'Logout' and e.activity = 'Login') as LogoutTime, 
    from eventlog e
    order by requestor, logintime
    

    【讨论】:

    • 谢谢,这已经很接近了。唯一的问题是 EventLog 中的 min(TimeOfAction) 部分,其中 TimeOfAction > e1.TimeOfAction 和 Activity = 'Logout' 返回第一个下一个注销。如果有重复的注销,我需要最后一个重复的注销,但显然将 min 更改为 max 只会获得整个数据集中最大的注销。
    • 是否可以在 where 子句中封装“下一次注销后登录”的逻辑,而不仅仅是“下一次大于当前登录记录的注销”?因为我认为必须两者兼而有之。因为如果有重复的注销(不仅仅是重复的登录),我需要获取这些重复项的最后一次注销,而不仅仅是集合中的下一个。 IE 如果我们有:登录注销注销(重复注销)
    • 是的。在这种情况下,将其更改为 max() 将不起作用。该查询只是将每个登录记录匹配到一个注销记录。如果我对您的理解正确,您希望最后一个 Logout 记录就在另一个 Login 记录之前,因为可能会发生多个 Logout?这对我来说不太有意义,用户如何在不重新登录的情况下多次注销?
    • 这是日志系统中的一个错误,正在处理但尚未修复,因此这是一个临时解决方案。
    【解决方案2】:
    select min(TimeOfAction) Login, null Logout, Requestor
      from EventLog
     where Activity = 'Login'
     group by Requestor
    union
    select null Login, max(TimeOfAction) Logout, Requestor
      from EventLog
     where Activity = 'Logout'
     group by Requestor
    

    【讨论】:

    • 我尝试了类似的方法并意识到每个请求者只会返回一行。我认为报告需要包含每个请求者的每个登录-注销记录。
    【解决方案3】:

    这里有一个选项供您使用一些 CTE 和 row_numbers。基本上,它为每个用户对事件进行排序,然后查找在注销后或不执行任何操作的登录列表,然后查找执行登录或不执行任何操作的注销列表,然后将它们关联成对。

    ;with events as (
      select *,
             row_number() over(partition by Requestor order by TimeOfAction) row
      from EventLog
    ), logins as (
      select e1.Activity, e1.TimeOfAction, e1.Requestor,
             row_number() over(partition by e1.Requestor order by e1.TimeOfAction) row
      from events e1
        left join events e2 on e1.Requestor=e2.Requestor
                           and e1.row=e2.row+1
      where e1.Activity='Login'
        and e1.Activity!=isnull(e2.Activity, 'Logout')
    ), logouts as (
      select e1.Activity, e1.TimeOfAction, e1.Requestor,
             row_number() over(partition by e1.Requestor order by e1.TimeOfAction) row
      from events e1
        left join events e2 on e1.Requestor=e2.Requestor
                           and e1.row=e2.row-1
      where e1.Activity='Logout'
        and e1.Activity!=isnull(e2.Activity, 'Login')
    )
    select i.Requestor, i.TimeOfAction as loginTime, o.TimeOfAction as logoutTime
    from logins i
      left join logouts o on i.Requestor=o.Requestor
                         and i.row=o.row
    

    注意:通过将部分或全部 CTE 查询拆分到临时表中,可以(大幅?)提高查询性能。即类似于以下内容:

    select *,
           row_number() over(partition by Requestor order by TimeOfAction) row
    into #events
    from EventLog
    
    select e1.Activity, e1.TimeOfAction, e1.Requestor,
           row_number() over(partition by e1.Requestor order by e1.TimeOfAction) row
    into #logins
    from #events e1
      left join #events e2 on e1.Requestor=e2.Requestor
                         and e1.row=e2.row+1
    where e1.Activity='Login'
      and e1.Activity!=isnull(e2.Activity, 'Logout')
    
    select e1.Activity, e1.TimeOfAction, e1.Requestor,
           row_number() over(partition by e1.Requestor order by e1.TimeOfAction) row
    into #logouts
    from #events e1
      left join #events e2 on e1.Requestor=e2.Requestor
                         and e1.row=e2.row-1
    where e1.Activity='Logout'
      and e1.Activity!=isnull(e2.Activity, 'Login')
    
    select i.Requestor, i.TimeOfAction as loginTime, o.TimeOfAction as logoutTime
    from #logins i
      left join #logouts o on i.Requestor=o.Requestor
                          and i.row=o.row
    
    drop table #logouts
    drop table #logins
    drop table #events
    

    【讨论】:

    • 这很好用,速度也快得惊人。不完全确定 CTE / 事件是什么,但我会调查一下。谢谢。
    • 没问题。 CTE(公用表表达式)本质上是 with 语法,用于分离子查询并只输入一次......至少这就是它在这里真正做的一切,但它也可以做其他事情(如递归查询)。
    • 我正在尝试将其转换为使用临时表,如您所说。我基本上会为每个表使用 SELECT INTO #temptable 语法而不是 WITH 关键字,其他所有内容都大致相似吗?尝试在我刚刚创建的临时表上执行 FROM 时收到错误“无效的对象名称”。
    • @sean,我已经编辑了我的答案,以包括一种使用临时表而不是 CTE 的可能方法。使用我放在一起的示例表,实际查询计划的百分比稍微好一些……但这对于亚秒级查询来说意义不大……
    • 在 #events 上执行 from 时出现一些错误,例如“无效的对象名称 #events”,此外,“多部分标识符”在显示 e1.(列)时无法绑定。似乎表一创建就被删除了——drop table 命令也显示错误,b/c 表不存在。我在网上阅读,它说这可能是权限问题或临时表范围的问题。我需要更改设置吗?我正在使用带有 SQL Express 2008 R2 的 SQL 服务器管理工​​作室。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多