我有一份报告,显示过去 12 小时内的事件计数,按小时分组。听起来很容易,但我正在努力解决的是如何包含填补空白的记录。
这是一个示例表:
Event
(
EventTime datetime,
EventType int
)
数据如下所示:
'2012-03-08 08:00:04', 1
'2012-03-08 09:10:00', 2
'2012-03-08 09:11:04', 2
'2012-03-08 09:10:09', 1
'2012-03-08 10:00:17', 4
'2012-03-08 11:00:04', 1
我需要创建一个结果集,该结果集在过去 12 小时中的每一小时都有一条记录,无论该小时内是否有事件。
假设当前时间是“2012-03-08 11:00:00”,报告将显示(大致):
Hour EventCount
---- ----------
23 0
0 0
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 1
9 3
10 1
我想出了一个解决方案,它使用一个表,该表在一天中的每个小时都有一条记录。我设法在 where 子句中使用 UNION 和一些复杂的案例逻辑来获得我正在寻找的结果,但我希望有人有一个更优雅的解决方案。
对于 SQL Server 2005+,您可以使用循环和递归 CTE 非常轻松地生成这 12 条记录。以下是递归 CTE 的示例:
然后,您只需将其与您的事件表一起加入即可。
Tally 表可以用于这样的事情。他们可以非常有效。创建下面的计数表。我为您的示例创建了只有 24 行的计数表,但是您可以使用任意数量的行来创建它以适应其他目的。
我假设您的表名为 dbo.tblEvents,请运行以下查询。我相信这就是您正在寻找的:
我相信归功于以下链接,我相信这是我第一次遇到这个问题的地方:
http://www.sqlservercentral.com/articles/T-SQL/62867/
http://www.sqlservercentral.com/articles/T-SQL/74118/
首先,我很抱歉自从我上次发表评论以来我的回复延迟。
该主题出现在评论中,即使用递归 CTE(从这里开始的 rCTE)运行速度足够快,因为行数很少。虽然看起来可能是这样,但事实并非如此。
构建计数表和计数功能
在开始测试之前,我们需要构建一个具有适当聚集索引和 Itzik Ben-Gan 风格的 Tally 函数的物理 Tally Table。我们还将在 TempDB 中完成所有这些操作,以免意外丢失任何人的好东西。
这是构建 Tally Table 的代码和我当前的 Itzik 精彩代码的生产版本。
顺便说一句……请注意,它构建了一百万零一行的 Tally 表,并在大约一秒左右的时间内向其中添加了一个聚集索引。用 rCTE 试试,看看需要多长时间!;-)
建立一些测试数据
我们还需要一些测试数据。是的,我同意我们将要测试的所有功能,包括 rCTE,仅在 12 行中运行在一毫秒或更短的时间内,但这是很多人陷入的陷阱。稍后我们将更多地讨论这个陷阱,但现在让我们模拟调用每个函数 40,000 次,这是关于我的商店中的某些函数在一天 8 小时内被调用的次数。试想一下,在大型在线零售业务中可能会调用多少次此类函数。
所以,这里是用随机日期构建 40,000 行的代码,每行都有一个行号,仅用于跟踪目的。我没有花时间把时间弄成整个小时,因为在这里没关系。
构建一些功能来做 12 行小时的事情
接下来,我将 rCTE 代码转换为一个函数并创建了 3 个其他函数。它们都被创建为高性能 iTVF(内联表值函数)。您总是可以分辨出来,因为 iTVF 没有像标量或 mTVF(多语句表值函数)那样的 BEGIN。
这是构建这 4 个函数的代码......我以它们使用的方法命名它们,而不是它们所做的,只是为了更容易识别它们。
构建测试工具来测试功能
最后但同样重要的是,我们需要一个测试工具。我进行基线检查,然后以相同的方式测试每个功能。
这是测试工具的代码...
在上面的测试工具中要注意的一件事是,我将所有输出分流到“一次性”变量中。这是为了尽量保持性能测量的纯正,而不会输出到磁盘或屏幕倾斜结果。
关于集合统计的注意事项
另外,对于潜在的测试人员来说,请注意......在测试标量或 mTVF 函数时,您不得使用 SET STATISTICS。它只能安全地用于本测试中的 iTVF 功能。SET STATISTICS 已被证明可以使 SCALAR 函数的运行速度比没有它时实际运行速度慢数百倍。是的,我正在尝试倾斜另一个风车,但那将是一篇完整的文章长度的帖子,我没有时间。我在 SQLServerCentral.com 上有一篇文章谈到了这一切,但是在这里发布链接是没有意义的,因为有人会因为它而变得不正常。
测试结果
因此,这是我在我的 6GB RAM 的小型 i5 笔记本电脑上运行测试工具时的测试结果。
仅选择数据的“基线选择”(每行创建 12 次以模拟相同的返回量)大约在 1/5 秒内出现。其他一切都在大约四分之一秒内出现。好吧,除了该死的 rCTE 功能之外的一切。耗时 4 又 1/4 秒或 16 倍(慢 1,600%)。
看看逻辑读取(内存 IO)...... rCTE 消耗了高达 2,960,000 次(几乎 300 万次读取),而其他功能仅消耗了大约 82,100 次。这意味着 rCTE 消耗的内存 IO 是其他任何函数的 34.3 倍以上。
结束思想
让我们总结一下。与其他任何函数相比,执行此“小型”12 行操作的 rCTE 方法使用的 CPU(和持续时间)多 16 倍(1,600%),内存 IO 多 34.3 倍(3,430%)。
Heh... I know what you're thinking. "Big Deal! It's just one function."
Yeah, agreed, but how many other functions do you have? How many other places outside of functions do you have? And do you have any of those that work with more than just 12 rows each run? And, is there any chance that someone in a lurch for a method might copy that rCTE code for something much bigger?
Ok, time to be blunt. It makes absolutely no sense for people to justify performance challenged code just because of supposed limited row counts or usage. Except for when you purchase an MPP box for perhaps millions of dollars (not to mention the expense of rewriting code to get it to work on such a machine), you can't buy a machine that runs your code 16 times faster (SSD's won't do it either... all this stuff was in high speed memory when we tested it). Performance is in the code. Good performance is in good code.
Can you imagine if all of your code ran "just" 16 times faster?
Never justify bad or performance challenged code on low rowcounts or even low usage. If you do, you might have to borrow one of the windmills I was accused of tilting at to keep your CPUs and disks cool enough. ;-)
A WORD ON THE WORD "TALLY"
Yeah... I agree. Semantically speaking, the Tally Table contains numbers, not "tallies". In my original article on the subject (it wasn't the original article on the technique but it was my first on it), I called it "Tally" not because of what it contains, but because of what it does... it's used to "count" instead of looping and to "Tally" something is to "Count" something. ;-) Call it what you will... Numbers Table, Tally Table, Sequence Table, whatever. I don't care. For me, "Tally" is more meaning full and, being a good lazy DBA, contains only 5 letters (2 are identical) instead of 7 and it's easier to say for most folks. It's also "singular", which follows my naming convention for tables. ;-) It's also what the article that contained a page from a book from the 60's called it. I'll always refer to it as a "Tally Table" and you'll still know what I or someone else means. I also avoid Hungarian Notation like the plague but called the function "fnTally" so that I could say "Well, if you used the eff-en Tally Function I showed you, you wouldn't have a performance problem" without it actually being an HR violation. ;-)
What I'm more concerned about is people learning to use it properly instead of resorting to things like performance challenged rCTEs and other forms of Hidden RBAR.
您需要
RIGHT JOIN
使用查询返回您需要的每小时一条记录的数据。有关获取行号的几种方法,请参见this ,然后您可以从当前时间减去几小时。
在 Oracle 中,对 dual 的分层查询将生成行: