【问题标题】:Rolling counts with TSQL使用 TSQL 滚动计数
【发布时间】:2017-05-30 03:04:14
【问题描述】:

首先让我先说一下我不完全确定如何首先提出这个问题,这一直是试图找到答案的一大障碍。因此,我可能使用了完全错误的术语。

我想在一段时间内使用窗口获取不同用户的计数。

我的数据表有以下几列:Id、User、RequestedOn、Query,其中请求被系统随时间捕获。例如,在 8 小时的过程中,78 位不同的用户向系统查询了 370 次不同的时间。

我想出了如何通过蛮力和忽略 (BF&I) 来做到这一点,但与许多 BF&I 方法一样,它无法扩展有价值的豆子。

在这些示例中,计数的窗口大小为 8 小时;给定 8 小时时段内不同用户的数量。

Select '5/28/17 15:00' [StartingFrom], Count(Distinct [UserName]) [Users] From [vwRequests] Where [RequestedOn] >= '5/28/17 15:00' And [RequestedOn] <= '5/28/17 23:00' Union
Select '5/28/17 14:00' [StartingFrom], Count(Distinct [UserName]) [Users] From [vwRequests] Where [RequestedOn] >= '5/28/17 14:00' And [RequestedOn] <= '5/28/17 22:00' Union
Select '5/28/17 13:00' [StartingFrom], Count(Distinct [UserName]) [Users] From [vwRequests] Where [RequestedOn] >= '5/28/17 13:00' And [RequestedOn] <= '5/28/17 21:00' Union
Select '5/28/17 12:00' [StartingFrom], Count(Distinct [UserName]) [Users] From [vwRequests] Where [RequestedOn] >= '5/28/17 12:00' And [RequestedOn] <= '5/28/17 20:00' Union
Select '5/28/17 11:00' [StartingFrom], Count(Distinct [UserName]) [Users] From [vwRequests] Where [RequestedOn] >= '5/28/17 11:00' And [RequestedOn] <= '5/28/17 19:00' Union
Select '5/28/17 10:00' [StartingFrom], Count(Distinct [UserName]) [Users] From [vwRequests] Where [RequestedOn] >= '5/28/17 10:00' And [RequestedOn] <= '5/28/17 18:00' Union
Select '5/28/17 09:00' [StartingFrom], Count(Distinct [UserName]) [Users] From [vwRequests] Where [RequestedOn] >= '5/28/17 09:00' And [RequestedOn] <= '5/28/17 17:00' Union
Select '5/28/17 08:00' [StartingFrom], Count(Distinct [UserName]) [Users] From [vwRequests] Where [RequestedOn] >= '5/28/17 08:00' And [RequestedOn] <= '5/28/17 16:00' 

我认为必须有更好的方法来做到这一点,但我什至不知道从哪里开始寻找。

指针会很棒!

【问题讨论】:

  • 你能提供一些示例数据和预期的输出吗?

标签: sql-server tsql sql-server-2012


【解决方案1】:

如果我理解正确,您需要这样的recursive cte

DECLARE @StartTime datetime = '2017-05-28 00:00:00'
DECLARE @EndTime datetime = '2017-05-29 00:00:00'

;WITH cte AS
(
    SELECT @StartTime AS StartPeriod, dateadd(hour,8,@StartTime) AS EndPeriod
    UNION ALL 
    SELECT dateadd(hour,1,StartPeriod), dateadd(hour,1,EndPeriod) AS EndPeriod
    FROM cte
    WHERE cte.StartPeriod < @EndTime
)
-- cte returns
--StartPeriod                EndPeriod
--2017-05-28 00:00:00.000   2017-05-28 08:00:00.000
--2017-05-28 01:00:00.000   2017-05-28 09:00:00.000
--2017-05-28 02:00:00.000   2017-05-28 10:00:00.000
--2017-05-28 03:00:00.000   2017-05-28 11:00:00.000
--2017-05-28 04:00:00.000   2017-05-28 12:00:00.000
--2017-05-28 05:00:00.000   2017-05-28 13:00:00.000
--.................
SELECT c.StartPeriod, c.EndPeriod, Users FROM cte c
OUTER APPLY (
             SELECT Count(Distinct [UserName]) AS Users -- i think you should use Count(distinct UserId) instead of UserName
             From [vwRequests] Where [RequestedOn] BETWEEN c.StartPeriod AND c.EndPeriod
          ) ca
OPTION (MAXRECURSION 0)

【讨论】:

  • 这很神奇,可以满足我的一切需求。它还向我介绍了 Common Table Expressions 的概念,我对此一无所知并且有更多的阅读要做。太感谢了! (是的,从长远来看,计算 UserId 可能会更快。)
【解决方案2】:

如果您想优化现有查询的性能而不进行太多更改,请将 UNION 替换为 UNION ALL 并在 Username 和 RequestedOn 列上添加一些索引。

如果vwRequests 是一个表格(不是一个视图),试试这些看看最适合你的:

CREATE INDEX IX1 ON dbo.vwRequests (RequestedOn, Username)
CREATE INDEX IX2 ON dbo.vwRequests (Username, RequestedOn)

如果vwRequests 是视图,您可以尝试在基表上添加索引或将视图更改为索引视图。

如果你想重写你的查询,你可以这样开始:

SELECT x1.StartingFrom, x2.Users
FROM (VALUES (8),(9),(10),(11),(12),(13),(14),(15)) h (h)
CROSS APPLY (
    SELECT DATEADD(HOUR,h,'20170528') AS [StartingFrom]
) x1
CROSS APPLY (
    SELECT COUNT(DISTINCT vr.Username) AS Users
    FROM dbo.vwRequests vr
    WHERE vr.RequestedOn BETWEEN x1.StartingFrom AND DATEADD(HOUR,8,x1.StartingFrom)
) x2

【讨论】:

  • 谢谢!这不是我所需要的;虽然它看起来不容易扩展,但它可以工作。然而,这给了我新的东西去查找、学习和深入研究。为此,我很感激,因此投了赞成票。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-09
  • 1970-01-01
  • 2015-10-09
  • 1970-01-01
  • 2016-11-19
  • 2019-01-03
相关资源
最近更新 更多