【问题标题】:SQL Server: row_number partitioned by timeoutSQL Server:row_number 按超时分区
【发布时间】:2012-04-21 08:16:39
【问题描述】:

我有一个包含一系列 (IP varchar(15), DateTime datetime2) 值的表。每行对应一个用户发出的 HTTP 请求。我想为这些行分配会话编号。不同的 IP 地址有不同的会话号。 如果最后一次请求超过 30 分钟,则应为同一 IP 分配一个新的会话号。这是一个示例输出:

IP,      DateTime,         SessionNumber, RequestNumber
1.1.1.1, 2012-01-01 00:01, 1,             1
1.1.1.1, 2012-01-01 00:02, 1,             2
1.1.1.1, 2012-01-01 00:03, 1,             3
1.1.1.2, 2012-01-01 00:04, 2,             1 --different IP => new session number
1.1.1.2, 2012-01-01 00:05, 2,             2
1.1.1.2, 2012-01-01 00:40, 3,             1 --same IP, but last request 35min ago (> 30min)

第 1 列和第 2 列是输入,第 3 和第 4 列是所需的输出。该表显示了两个用户。

由于底层表确实很大,如何有效解决此问题?我希望对数据进行少量恒定的传递(一到两次)。

【问题讨论】:

  • 什么版本的 SQL Server?如果 2012 年新的 OVER 子句功能会有所帮助。

标签: sql-server tsql sql-server-2012 olap row-number


【解决方案1】:

这里有几个尝试。

;WITH CTE1 AS
(
SELECT *,
IIF(DATEDIFF(MINUTE,
       LAG(DateTime) OVER (PARTITION BY IP ORDER BY DateTime),
       DateTime) < 30,0,1) AS SessionFlag
FROM Sessions
), CTE2 AS
(
SELECT *,
       SUM(SessionFlag) OVER (PARTITION BY IP 
                                  ORDER BY DateTime) AS IPSessionNumber
FROM CTE1
)
SELECT IP,
       DateTime,
       DENSE_RANK() OVER (ORDER BY IP, IPSessionNumber) AS SessionNumber,
       ROW_NUMBER() OVER (PARTITION BY IP, IPSessionNumber 
                              ORDER BY DateTime) AS RequestNumber
FROM CTE2

这有两个排序操作(由IP, DateTime 然后由IP, IPSessionNumber),但假设SessionNumber 可以任意分配,只要为每个IP 地址的每个新会话分配不同的唯一会话编号/ 30 分钟规则。

按时间顺序依次分配SessionNumbers。我使用了以下内容。

;WITH CTE1 AS
(
SELECT *,
IIF(DATEDIFF(MINUTE,
       LAG(DateTime) OVER (PARTITION BY IP ORDER BY DateTime),
       DateTime) < 30,0,1) AS SessionFlag
FROM Sessions
), CTE2 AS(
SELECT *,
       SUM(SessionFlag) OVER (ORDER BY DateTime) AS GlobalSessionNo
FROM CTE1
), CTE3 AS(
SELECT *,
       MAX(CASE WHEN SessionFlag = 1 THEN GlobalSessionNo END) 
               OVER (PARTITION BY IP ORDER BY DateTime) AS SessionNumber
FROM CTE2)
SELECT IP,
       DateTime,
       SessionNumber,
       ROW_NUMBER() OVER (PARTITION BY SessionNumber 
                              ORDER BY DateTime) AS RequestNumber
FROM CTE3

不过,这会将排序操作的数量增加到 4 个。

【讨论】:

  • 如果来自两个IP的请求交错,他们的会话不会混淆吗?
  • 我必须说 T-SQL 缺少自定义的、有状态的聚合功能。这将使这个查询变得简单,并且只需要一个排序操作。
  • @usr - 也可能值得评估游标和#temp 表!不确定这是否是 OVER clause enhancement request - Progressive ordered calculations 会有所帮助的地方。
  • 我实际上做了一个动态光标解决方案,同时希望你能发布一个单查询解决方案(你做到了!)。它慢了 20 倍。也许我仍然会使用它,因为我可以通过更复杂的计算更好地扩展它。我们在这里所做的基本操作是“扫描”(例如msdn.microsoft.com/en-us/library/hh211665(VS.103).aspx)。 scan = 对有序序列的任意聚合,同时为每个输入元素输出一个元素,而不是为整个组/序列输出一个元素。
【解决方案2】:

这是一个使用表变量和 row_number 来创建可在递归 CTE 中使用的 ID 的版本。将性能与游标和一个查询(由 Martin 提供)版本进行比较可能是值得的。

CREATE TABLE #T
(
  IP varchar(15),
  DateTime datetime,
  ID int,
  primary key (IP, ID)
)

insert into #T(IP, DateTime, ID)
select IP, DateTime, row_number() over(partition by IP order by DateTime) 
from #sessionRequests

;with C as
(
  select IP,
         ID,
         DateTime,
         1 as Session
  from #T
  where ID = 1
  union all 
  select T.IP,
         T.ID,
         T.DateTime,
         C.Session + case when datediff(minute, C.DateTime, T.DateTime) >= 30 then 1 else 0 end
  from #T as T
    inner join C 
      on T.IP = C.IP and
         T.ID = C.ID + 1
)
SELECT IP,
       DateTime,
       dense_rank() over(order by IP, Session) as SessionNumber,
       row_number() over(partition by IP, Session order by DateTime) as RequestNumber
from C
order by IP, DateTime, SessionNumber, RequestNumber
option (maxrecursion 0)

【讨论】:

  • 我喜欢这个版本,因为它易于扩展,几乎就像基于光标的方法。我将其更改为使用修复优化器问题的临时表(表变量没有统计信息)。此外,我验证了此代码有效。谢谢!
猜你喜欢
  • 2021-06-22
  • 1970-01-01
  • 1970-01-01
  • 2017-07-02
  • 2015-07-02
  • 2016-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多