use*_*633 5 performance sql-server stored-procedures query-performance
我有一个供应商应用程序,它将会话数据存储在 SQL Server 2008 数据库表中。它包含的列包括 sessionId、用户的 IP 地址、会话创建时的日期时间戳(即用户登录)以及会话销毁时的日期时间戳(即用户注销或系统注销用户) .
我的目标是分析该表中的所有记录,并找出所有记录中并发会话的平均数。
现在,不幸的是,由于本问题范围之外的原因,会话销毁日期不准确。因此,我对会话的持续时间使用了一个非常粗略的估计:1 小时。在我整理了设计阶段之后,我可以随时更改数字。
我确信我可以组合一个存储过程来获得平均并发会话数,但我希望我可以通过查询来完成它。
为了简化这里的事情,让我们假设表中有 5 条记录,并且所有记录都是在同一天创建的,并且是 GMT 时间:
sessionId sessionStart sessionEnd Accumulative # of Concurrent Sessions
1 12:00 13:00 1
2 12:15 13:15 2
3 12:30 13:30 3
4 12:45 13:45 4
5 13:00 14:00 4
Run Code Online (Sandbox Code Playgroud)
13:00,第一个会话被销毁。并发会话数保持在 4,因为会话 2 到 5 仍然存在。
问题是我如何编写一个查询来输出平均并发会话数?可以做到吗?我想它会涉及同一张桌子上的多个连接,但我还没有完全弄清楚从哪里开始。
该表有不到一百万条记录。我可以使用 2012 年的盒子,如果有帮助,可以将表格复制到那里。
我发现 SQL Server 2012 更适合解决此类问题,因为它支持窗口聚合ORDER BY的OVER子句SUM。将数据放入临时表:
CREATE TABLE #my_sessions (sessionId INT, sessionStart DATETIME);
INSERT INTO #my_sessions VALUES
(1, '20180413 12:00:00'),
(2, '20180413 12:15:00'),
(3, '20180413 12:30:00'),
(4, '20180413 12:45:00'),
(5, '20180413 13:00:00');
Run Code Online (Sandbox Code Playgroud)
我将把查询分成三个部分,这样更容易理解。第一个技巧使用运行总数来获取每次更改时的并发会话数。想象一下,获取您的数据,为创建会话的行分配一个 1,并为被破坏的行制作它的第二个副本,并为这些行分配一个 -1。如果您计算按时间排序的运行总数,您最终会得到每次值更改时的活动会话数。
SELECT DISTINCT
event_time
, SUM(event_change) OVER (ORDER BY event_time RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) active_sessions
FROM #my_sessions
CROSS APPLY (
VALUES
(sessionStart, 1),
(DATEADD(HOUR, 1, sessionStart), -1)
) ca (event_time, event_change);
Run Code Online (Sandbox Code Playgroud)
结果如下:
?????????????????????????????????????????????
? event_time ? active_sessions ?
?????????????????????????????????????????????
? 2018-04-13 12:00:00.000 ? 1 ?
? 2018-04-13 12:15:00.000 ? 2 ?
? 2018-04-13 12:30:00.000 ? 3 ?
? 2018-04-13 12:45:00.000 ? 4 ?
? 2018-04-13 13:00:00.000 ? 4 ?
? 2018-04-13 13:15:00.000 ? 3 ?
? 2018-04-13 13:30:00.000 ? 2 ?
? 2018-04-13 13:45:00.000 ? 1 ?
? 2018-04-13 14:00:00.000 ? 0 ?
?????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)
现在我们需要取该值的平均值。我假设您想要按时间加权的平均值,因此缺少的是每次测量应计入的分钟数。SQL Server 2012 引入了LEAD使这变得非常容易的函数。现在查询如下:
SELECT
active_sessions
, DATEDIFF(MINUTE, event_time, LEAD(event_time) OVER (ORDER BY event_time)) minutes_until_change
FROM
(
SELECT DISTINCT
event_time
, SUM(event_change) OVER (ORDER BY event_time RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) active_sessions
FROM #my_sessions
CROSS APPLY (
VALUES
(sessionStart, 1),
(DATEADD(HOUR, 1, sessionStart), -1)
) ca (event_time, event_change)
) active_sessions
Run Code Online (Sandbox Code Playgroud)
中间结果集:
??????????????????????????????????????????
? active_sessions ? minutes_until_change ?
??????????????????????????????????????????
? 1 ? 15 ?
? 2 ? 15 ?
? 3 ? 15 ?
? 4 ? 15 ?
? 4 ? 15 ?
? 3 ? 15 ?
? 2 ? 15 ?
? 1 ? 15 ?
? 0 ? NULL ?
??????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)
我们需要计算平均值,这是最简单的部分。把它们放在一起:
SELECT 1.0 * SUM(active_sessions * minutes_until_change) / SUM(minutes_until_change)
FROM
(
SELECT
active_sessions
, DATEDIFF(MINUTE, event_time, LEAD(event_time) OVER (ORDER BY event_time)) minutes_until_change
FROM
(
SELECT DISTINCT
event_time
, SUM(event_change) OVER (ORDER BY event_time RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) active_sessions
FROM #my_sessions
CROSS APPLY (
VALUES
(sessionStart, 1),
(DATEADD(HOUR, 1, sessionStart), -1)
) ca (event_time, event_change)
) active_sessions
) average_me
WHERE minutes_until_change IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)
最终结果是 2.5。