您知道过去12小时内每小时产生一条记录的简单方法吗?


12

我有一个报告,其中显示了过去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和一些复杂的大小写逻辑设法获得了我想要的结果,但是我希望有人能有一个更优雅的解决方案。

Answers:


21

对于SQL Server 2005+,您可以使用循环或递归CTE轻松生成这12条记录。这是递归CTE的示例:

DECLARE @Date DATETIME
SELECT @Date = '20120308 11:00:00'

;WITH Dates AS
(
    SELECT DATEPART(HOUR,DATEADD(HOUR,-1,@Date)) [Hour], 
      DATEADD(HOUR,-1,@Date) [Date], 1 Num
    UNION ALL
    SELECT DATEPART(HOUR,DATEADD(HOUR,-1,[Date])), 
      DATEADD(HOUR,-1,[Date]), Num+1
    FROM Dates
    WHERE Num <= 11
)
SELECT [Hour], [Date]
FROM Dates

然后,您只需将其加入事件表即可。


2
我在发布后发现了这一点。 说明extended.com/2009/10/21/… 它表示为此目的使用CTE比存储表的效率低。这是真的?正如Nick所说,在这种情况下可能无关紧要,但是……
Leigh Riffel 2012年

4
我认为如果行数更多,这会有所不同,如果您需要12条记录,那么根本不会影响性能
Lamak 2012年

Lamak和@swasheck。嘿...我有点晚了(对这个线程的迷路),但是没问题。请参阅上面我最终发布的支持我的主张的答案。请记住,所有代码都有累积作用。如果人们编写的所有代码“快”了16倍,那么在这样的论坛上的一半帖子就不再需要了。而且,不需要花费更多的时间(有时更短)来编写更快的代码。
杰夫·摩登

10

提示表可以用于这样的事情。它们可能非常有效。在下面创建统计表。在您的示例中,我创建的计数表只有24行,但是您可以创建许多表来满足其他目的。

SELECT TOP 24 
        IDENTITY(INT,1,1) AS N
   INTO dbo.Tally
   FROM Master.dbo.SysColumns sc1,
        Master.dbo.SysColumns sc2

--===== Add a Primary Key to maximize performance
  ALTER TABLE dbo.Tally
    ADD CONSTRAINT PK_Tally_N 
        PRIMARY KEY CLUSTERED (N) WITH FILLFACTOR = 100

我假设您的表名为dbo.tblEvents,请运行以下查询。我相信这是您要寻找的:

SELECT t.n, count(e.EventTime)
FROM dbo.Tally t
LEFT JOIN dbo.tblEvent e  on t.n = datepart(hh, e.EventTime)
GROUP BY t.n
ORDER BY t.n

我相信功劳归功于以下链接,我相信这是我第一次遇到这个问题:

http://www.sqlservercentral.com/articles/T-SQL/62867/

http://www.sqlservercentral.com/articles/T-SQL/74118/


+1,但从语义上讲是数字表,而不是统计表。
亚伦·伯特兰

1
“计数”的定义之一是“计数”。“ Tally Table”以“ Tally Stick”命名,Tally Stick是用于计数的长而细的棍子。
杰夫·摩登

7

首先,对于自上次发表评论以来拖延回复我深表歉意。

该主题出现在评论中,由于行数少,因此使用递归CTE(此处为rCTE)运行速度足够快。尽管看起来可能是这样,但事实并非如此。

建立合计表和​​合计功能

在开始测试之前,我们需要使用适当的聚簇索引和Itzik Ben-Gan风格的提示功能构建物理提示表。我们还将在TempDB中进行所有这些操作,以免意外丢弃任何人的好东西。

这是构建Tally Table的代码,以及我当前生产的Itzik出色代码的版本。

--===== Do this in a nice, safe place that everyone has
    USE tempdb
;
--===== Create/Recreate a Physical Tally Table
     IF OBJECT_ID('dbo.Tally','U') IS NOT NULL
        DROP TABLE dbo.Tally
;
     -- Note that the ISNULL makes a NOT NULL column
 SELECT TOP 1000001
        N = ISNULL(ROW_NUMBER() OVER (ORDER BY (SELECT NULL))-1,0)
   INTO dbo.Tally
   FROM      sys.all_columns ac1
  CROSS JOIN sys.all_columns ac2
;
  ALTER TABLE dbo.Tally
    ADD CONSTRAINT PK_Tally PRIMARY KEY CLUSTERED (N)
;
--===== Create/Recreate a Tally Function
     IF OBJECT_ID('dbo.fnTally','IF') IS NOT NULL
        DROP FUNCTION dbo.fnTally
;
GO
 CREATE FUNCTION [dbo].[fnTally]
/**********************************************************************************************************************
 Purpose:
 Return a column of BIGINTs from @ZeroOrOne up to and including @MaxN with a max value of 1 Trillion.

 As a performance note, it takes about 00:02:10 (hh:mm:ss) to generate 1 Billion numbers to a throw-away variable.

 Usage:
--===== Syntax example (Returns BIGINT)
 SELECT t.N
   FROM dbo.fnTally(@ZeroOrOne,@MaxN) t
;

 Notes:
 1. Based on Itzik Ben-Gan's cascading CTE (cCTE) method for creating a "readless" Tally Table source of BIGINTs.
    Refer to the following URLs for how it works and introduction for how it replaces certain loops. 
    http://www.sqlservercentral.com/articles/T-SQL/62867/
    http://sqlmag.com/sql-server/virtual-auxiliary-table-numbers
 2. To start a sequence at 0, @ZeroOrOne must be 0 or NULL. Any other value that's convertable to the BIT data-type
    will cause the sequence to start at 1.
 3. If @ZeroOrOne = 1 and @MaxN = 0, no rows will be returned.
 5. If @MaxN is negative or NULL, a "TOP" error will be returned.
 6. @MaxN must be a positive number from >= the value of @ZeroOrOne up to and including 1 Billion. If a larger
    number is used, the function will silently truncate after 1 Billion. If you actually need a sequence with
    that many values, you should consider using a different tool. ;-)
 7. There will be a substantial reduction in performance if "N" is sorted in descending order.  If a descending 
    sort is required, use code similar to the following. Performance will decrease by about 27% but it's still
    very fast especially compared with just doing a simple descending sort on "N", which is about 20 times slower.
    If @ZeroOrOne is a 0, in this case, remove the "+1" from the code.

    DECLARE @MaxN BIGINT; 
     SELECT @MaxN = 1000;
     SELECT DescendingN = @MaxN-N+1 
       FROM dbo.fnTally(1,@MaxN);

 8. There is no performance penalty for sorting "N" in ascending order because the output is explicity sorted by
    ROW_NUMBER() OVER (ORDER BY (SELECT NULL))

 Revision History:
 Rev 00 - Unknown     - Jeff Moden 
        - Initial creation with error handling for @MaxN.
 Rev 01 - 09 Feb 2013 - Jeff Moden 
        - Modified to start at 0 or 1.
 Rev 02 - 16 May 2013 - Jeff Moden 
        - Removed error handling for @MaxN because of exceptional cases.
 Rev 03 - 22 Apr 2015 - Jeff Moden
        - Modify to handle 1 Trillion rows for experimental purposes.
**********************************************************************************************************************/
        (@ZeroOrOne BIT, @MaxN BIGINT)
RETURNS TABLE WITH SCHEMABINDING AS 
 RETURN WITH
  E1(N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
            SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
            SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
            SELECT 1)                                  --10E1 or 10 rows
, E4(N) AS (SELECT 1 FROM E1 a, E1 b, E1 c, E1 d)      --10E4 or 10 Thousand rows
,E12(N) AS (SELECT 1 FROM E4 a, E4 b, E4 c)            --10E12 or 1 Trillion rows                 
            SELECT N = 0 WHERE ISNULL(@ZeroOrOne,0)= 0 --Conditionally start at 0.
             UNION ALL 
            SELECT TOP(@MaxN) N = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E12 -- Values from 1 to @MaxN
;
GO

顺便说一下,请注意,它建立了一个百万行的Tally表,并在大约一秒钟的时间内向其中添加了聚簇索引。用rCTE尝试一下,看看需要多长时间!;-)

建立一些测试数据

我们还需要一些测试数据。是的,我同意我们要测试的所有功能(包括rCTE)仅在一毫秒或更短的时间内才能运行12行,但这是很多人陷入的陷阱。稍后,我们将详细讨论该陷阱,但现在,让我们模拟每个函数调用40,000次,这相当于我的商店中的某些函数在一天8小时内被调用多少次。试想一下,在大型的在线零售业务中可能会调用这些功能多少次。

因此,这是构建具有随机日期的40,000行的代码,每个行都有一个行号仅用于跟踪目的。我没有花时间来整个小时,因为这无关紧要。

--===== Do this in a nice, safe place that everyone has
    USE tempdb
;
--===== Create/Recreate a Test Date table
     IF OBJECT_ID('dbo.TestDate','U') IS NOT NULL
        DROP TABLE dbo.TestDate
;
DECLARE  @StartDate DATETIME
        ,@EndDate   DATETIME
        ,@Rows      INT
;
 SELECT  @StartDate = '2010' --Inclusive
        ,@EndDate   = '2020' --Exclusive
        ,@Rows      = 40000  --Enough to simulate an 8 hour day where I work
;
 SELECT  RowNum       = IDENTITY(INT,1,1)
        ,SomeDateTime = RAND(CHECKSUM(NEWID()))*DATEDIFF(dd,@StartDate,@EndDate)+@StartDate
   INTO dbo.TestDate
   FROM dbo.fnTally(1,@Rows)
;

建立一些功能来做12行小时的事情

接下来,我将rCTE代码转换为一个函数并创建了3个其他函数。它们都是作为高性能iTVF(内联表值函数)创建的。您总能说出来,因为iTVF从来没有像标量或mTVF(多语句表值函数)那样具有BEGIN。

这是构建这4个函数的代码...我用它们使用的方法命名它们,而不是仅仅为了使它们易于识别而做的事情。

--=====  CREATE THE iTVFs
--===== Do this in a nice, safe place that everyone has
    USE tempdb
;
-----------------------------------------------------------------------------------------
     IF OBJECT_ID('dbo.OriginalrCTE','IF') IS NOT NULL
        DROP FUNCTION dbo.OriginalrCTE
;
GO
 CREATE FUNCTION dbo.OriginalrCTE
        (@Date DATETIME)
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
WITH Dates AS
(
    SELECT DATEPART(HOUR,DATEADD(HOUR,-1,@Date)) [Hour], 
      DATEADD(HOUR,-1,@Date) [Date], 1 Num
    UNION ALL
    SELECT DATEPART(HOUR,DATEADD(HOUR,-1,[Date])), 
      DATEADD(HOUR,-1,[Date]), Num+1
    FROM Dates
    WHERE Num <= 11
)
SELECT [Hour], [Date]
FROM Dates
GO
-----------------------------------------------------------------------------------------
     IF OBJECT_ID('dbo.MicroTally','IF') IS NOT NULL
        DROP FUNCTION dbo.MicroTally
;
GO
 CREATE FUNCTION dbo.MicroTally
        (@Date DATETIME)
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
 SELECT  [Hour] = DATEPART(HOUR,DATEADD(HOUR,t.N,@Date))
        ,[DATE] = DATEADD(HOUR,t.N,@Date)
   FROM (VALUES (-1),(-2),(-3),(-4),(-5),(-6),(-7),(-8),(-9),(-10),(-11),(-12))t(N)
;
GO
-----------------------------------------------------------------------------------------
     IF OBJECT_ID('dbo.PhysicalTally','IF') IS NOT NULL
        DROP FUNCTION dbo.PhysicalTally
;
GO
 CREATE FUNCTION dbo.PhysicalTally
        (@Date DATETIME)
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
 SELECT  [Hour] = DATEPART(HOUR,DATEADD(HOUR,-t.N,@Date))
        ,[DATE] = DATEADD(HOUR,-t.N,@Date)
   FROM dbo.Tally t
  WHERE N BETWEEN 1 AND 12
;
GO
-----------------------------------------------------------------------------------------
     IF OBJECT_ID('dbo.TallyFunction','IF') IS NOT NULL
        DROP FUNCTION dbo.TallyFunction
;
GO
 CREATE FUNCTION dbo.TallyFunction
        (@Date DATETIME)
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
 SELECT  [Hour] = DATEPART(HOUR,DATEADD(HOUR,-t.N,@Date))
        ,[DATE] = DATEADD(HOUR,-t.N,@Date)
   FROM dbo.fnTally(1,12) t
;
GO

建立测试线束以测试功能

最后但并非最不重要的一点是,我们需要一个测试工具。我进行基线检查,然后以相同的方式测试每个功能。

这是测试工具的代码...

PRINT '--========== Baseline Select =================================';
DECLARE @Hour INT, @Date DATETIME
;
    SET STATISTICS TIME,IO ON;
 SELECT  @Hour = RowNum
        ,@Date = SomeDateTime
   FROM dbo.TestDate
  CROSS APPLY dbo.fnTally(1,12);
    SET STATISTICS TIME,IO OFF;
GO
PRINT '--========== Orginal Recursive CTE ===========================';
DECLARE @Hour INT, @Date DATETIME
;

    SET STATISTICS TIME,IO ON;
 SELECT  @Hour = fn.[Hour]
        ,@Date = fn.[Date]
   FROM dbo.TestDate td
  CROSS APPLY dbo.OriginalrCTE(td.SomeDateTime) fn;
    SET STATISTICS TIME,IO OFF;
GO
PRINT '--========== Dedicated Micro-Tally Table =====================';
DECLARE @Hour INT, @Date DATETIME
;

    SET STATISTICS TIME,IO ON;
 SELECT  @Hour = fn.[Hour]
        ,@Date = fn.[Date]
   FROM dbo.TestDate td
  CROSS APPLY dbo.MicroTally(td.SomeDateTime) fn;
    SET STATISTICS TIME,IO OFF;
GO
PRINT'--========== Physical Tally Table =============================';
DECLARE @Hour INT, @Date DATETIME
;
    SET STATISTICS TIME,IO ON;
 SELECT  @Hour = fn.[Hour]
        ,@Date = fn.[Date]
   FROM dbo.TestDate td
  CROSS APPLY dbo.PhysicalTally(td.SomeDateTime) fn;
    SET STATISTICS TIME,IO OFF;
GO
PRINT'--========== Tally Function ===================================';
DECLARE @Hour INT, @Date DATETIME
;
    SET STATISTICS TIME,IO ON;
 SELECT  @Hour = fn.[Hour]
        ,@Date = fn.[Date]
   FROM dbo.TestDate td
  CROSS APPLY dbo.TallyFunction(td.SomeDateTime) fn;
    SET STATISTICS TIME,IO OFF;
GO

在上面的测试工具中要注意的一件事是,我将所有输出均分流到“丢弃”变量中。这样做是为了使性能测量尽可能纯净,而不会对磁盘​​或屏幕产生任何歪斜结果。

关于集合统计的注意事项

另外,对可能要测试的人也要当心……测试标量或mTVF函数时,切勿使用SET STATISTICS。它只能安全​​地用于iTVF功能(如本测试中的功能)。实践证明,SET STATISTICS使SCALAR函数的运行速度比不使用SCALAR函数时实际运行速度慢数百倍。是的,我正在尝试倾斜另一台风车,但这将是一篇完整的“篇幅长的文章”,而我没有时间。我在SQLServerCentral.com上有一篇文章都在谈论这一切,但是在此处发布链接毫无意义,因为有人会对此一无所知。

测试结果

因此,这是在装有6GB RAM的小型i5笔记本电脑上运行测试工具时的测试结果。

--========== Baseline Select =================================
Table 'Worktable'. Scan count 1, logical reads 82309, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'TestDate'. Scan count 1, logical reads 105, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 203 ms,  elapsed time = 206 ms.
--========== Orginal Recursive CTE ===========================
Table 'Worktable'. Scan count 40001, logical reads 2960000, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'TestDate'. Scan count 1, logical reads 105, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 4258 ms,  elapsed time = 4415 ms.
--========== Dedicated Micro-Tally Table =====================
Table 'Worktable'. Scan count 1, logical reads 81989, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'TestDate'. Scan count 1, logical reads 105, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 234 ms,  elapsed time = 235 ms.
--========== Physical Tally Table =============================
Table 'Worktable'. Scan count 1, logical reads 81989, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'TestDate'. Scan count 1, logical reads 105, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Tally'. Scan count 1, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 250 ms,  elapsed time = 252 ms.
--========== Tally Function ===================================
Table 'Worktable'. Scan count 1, logical reads 81989, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'TestDate'. Scan count 1, logical reads 105, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 250 ms,  elapsed time = 253 ms.

仅选择数据(每行创建12次以模拟相同的收益量)的“ BASELINE SELECT”大约在1/5秒之内。其他所有内容大约需要四分之一秒。好吧,除了血腥的rCTE功能外,其他所有功能。花了4到1/4秒或16倍的时间(慢了1600%)。

再看一下逻辑读取(内存IO)... rCTE消耗了296万(近300万次读取),而其他功能仅消耗了82100。这意味着rCTE消耗的内存IO是其他任何功能的34.3倍以上。

结束思想

让我们总结一下。用于执行此“小型” 12行操作的rCTE方法比其他任何功能使用的CPU(和持续时间)多了16倍(1600%),而内存IO则多了34.3倍(3,430%)。

嘿...我知道你在想什么。“大不了!这只是一项功能。”

是的,同意,但是您还有多少其他功能?除了功能,您还有其他几个地方?而且,您是否有其中每次运行仅超过12行的那些?而且,是否有可能某个人一头雾水地某个方法可能会复制rCTE代码以获得更大的东西?

好吧,时间直率。人们仅仅因为有限的行数或使用量来证明性能受到挑战的代码是绝对没有道理的。除了当您以数百万美元的价格购买MPP盒(更不用说重写代码以使其在这样的机器上工作而花费的费用)之外,您无法购买运行代码速度提高16倍的机器(赢得了SSD)也不做...所有这些东西在我们测试时都在高速内存中。性能在代码中。好的性能就是好的代码。

您是否可以想象所有代码的运行速度“快”了16倍?

不要以低的行数甚至低的使用率来证明糟糕的代码或性能受到挑战的代码。如果这样做,您可能不得不借用我被指控倾斜的一台风车,以保持CPU和磁盘足够凉爽。;-)

在单词“ TALLY”上的单词

是的,我同意。从语义上讲,理货表包含数字,而不是“总数”。在我关于该主题的原始文章中(不是有关该技术的原始文章,但这是我的第一篇文章),我之所以称其为“ Tally”,并不是因为它包含的内容,而是因为它的作用...它是用来“计数”而不是循环,用来“计数”某物就是“计数”某物。;-)称呼您将要...数字表,理货表,序列表等。我不在乎 对我来说,“ Tally”更有意义,它是一个很好的懒惰DBA,只包含5个字母(2个相同)而不是7个字母,对于大多数人来说更容易说出来。它也是“单数的”,遵循我对表的命名约定。;-) 包含60年代一本书中的一页的文章也称为它。我将始终将其称为“理货单”,您仍然会知道我或别人的意思。我也避免像瘟疫这样的匈牙利符号,而是将函数称为“ fnTally”,这样我可以说:“好吧,如果您使用了我展示给您的eff-en Tally函数,那么您就不会遇到性能问题”违反人力资源。;-) 没有实际违反人力资源。;-) 没有实际违反人力资源。;-)

我更关心的是人们学会正确使用它,而不是诉诸性能受到挑战的rCTE和其他形式的Hidden RBAR。


2

您需要RIGHT JOIN通过查询返回每小时每条记录一条记录的数据。

请参见此以几种方式获取行号,然后可以从当前时间中减去小时。

在Oracle中,对偶的分层查询将生成行:

SELECT to_char(sysdate-level/24,'HH24') FROM dual CONNECT BY Level <=24;

这是我遇到问题的“查询每小时返回一条记录”。只是想办法找出过去12(或24)小时中的每一小时生成12(或24)条记录的方法。
datagod 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.