過去12時間の1時間ごとに1つのレコードを生成する簡単な方法を知っていますか?


12

過去12時間のイベント数を1時間ごとにグループ化したレポートがあります。簡単に聞こえますが、私が苦労しているのは、ギャップをカバーするレコードを含める方法です。

以下にテーブルの例を示します。

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時間ごとに1つのレコードを持つ結果セットを作成する必要があります。

現在の時刻が「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

1時間ごとに1つのレコードを持つテーブルを使用するソリューションを思い付きました。UNIONとwhere句の複雑なケースロジックを使用して探していた結果を得ることができましたが、誰かがよりエレガントなソリューションを持っていることを望んでいました。

回答:


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
あなたが投稿した直後にこれを見つけました。 explainextended.com/2009/10/21/... これは、この目的のためにCTEを使用して格納されたテーブルよりも効率が低いことを示しています。これは本当ですか?ニックが言ったように、それはおそらく、このケースでは問題ありませんが、...
リーRiffel

4
12のレコードが必要な場合、行の数が多くなると違いが生じると思います。パフォーマンスにまったく影響はありません
-Lamak

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
「集計」の定義の1つは「カウントする」です。「タリーテーブル」は、「タリースティック」にちなんで名付けられました。「タリースティック」は、カウントに使用される細長い細いスティックです。
ジェフモデン

7

最初に、最後のコメントからの応答が遅れたことをおologiesびします。

件名は、行数が少ないため、再帰的CTE(以降、rCTE)を使用すると十分に速く実行されるというコメントで出てきました。そのように見えるかもしれませんが、真実から遠く離れることはできません。

タリーテーブルとタリー機能を構築する

テストを開始する前に、適切なクラスター化インデックスとItzik Ben-Ganスタイルの集計関数を使用して物理集計表を作成する必要があります。また、TempDBでこれらすべてを実行して、誰かのグッズを誤って削除しないようにします。

Tallyテーブルを作成するためのコードと、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

ちなみに、数百万行のタリーテーブルを作成し、クラスター化インデックスを約1秒ほどで追加したことに注目してください。rCTEで試してみて、どれくらい時間がかかるかを確認してください!;-)

いくつかのテストデータを作成する

また、いくつかのテストデータも必要です。はい、rCTEを含め、テストするすべての機能がわずか12行で1ミリ秒以下で実行されることに同意しますが、これは多くの人が陥るtrapです。このトラップについては後で詳しく説明しますが、ここでは、各関数の呼び出しを40,000回シミュレートします。これは、私のショップの特定の関数が1日8時間に呼び出される回数です。大規模なオンライン小売業でこのような関数が何回呼び出されるか想像してみてください。

そこで、ランダムな日付で40,000行を作成するコードを以下に示します。各行には、追跡のためだけに行番号があります。ここでは重要ではないので、時間を丸1時間とすることはしませんでした。

--===== 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にはScalarやmTVF(Multi-statement Table Valued Functions)のように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

上記のテストハーネスで注目すべきことは、すべての出力を「スローアウェイ」変数に分流していることです。これは、ディスクや画面への出力結果を歪めることなく、パフォーマンス測定を可能な限り純粋に保つことを試みることです。

設定統計に関する注意事項

また、テスターに​​なるための注意事項... ScalarまたはmTVF関数のいずれかをテストする場合は、SET STATISTICSを使用しないでください。このテストのようなiTVF機能でのみ安全に使用できます。SET STATISTICSは、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.

データのみを選択する「BASELINE SELECT」(各行が12回作成され、同じ収益のボリュームをシミュレートしました)が、約1/5秒で正しく入力されました。それ以外のすべては、約4分の1秒で到着しました。まあ、その血まみれのrCTE機能以外はすべて。4秒と1/4秒、または16倍長くなりました(1,600%遅くなりました)。

論理読み取り(メモリIO)を見てください。rCTEはなんと2,960,000(ほぼ300万回の読み取り)を消費しましたが、他の機能は約82,100しか消費しませんでした。つまり、rCTEは他のどの機能よりも34.3倍以上のメモリIOを消費しました。

締めくくり

まとめましょう。この「小さな」12行の処理を行うrCTEメソッドは、他のどの機能よりも16時間(1,600%)多いCPU(および期間)と34.3時間(3,430%)多いメモリIOを使用しました。

へえ...あなたの考えを知っています。「大したことだ!それはただ一つの機能だ」

ええ、同意しましたが、他にいくつの機能がありますか?関数以外の場所は他にいくつありますか?また、実行ごとに12行以上で動作するものはありますか?そして、メソッドを探している人がそのrCTEコードをもっと大きな何かのためにコピーする可能性はありますか?

さて、鈍い時間です。行数や使用量が制限されていると思われるからといって、パフォーマンスが問題になっているコードを正当化することはまったく意味がありません。おそらく数百万ドルでMPPボックスを購入する場合を除き(そのようなマシンで動作させるためにコードを書き換える費用は言うまでもありません)、コードを16倍高速で実行するマシンを購入することはできません(SSDが勝ちます私たちがそれをテストしたとき、これらはすべて高速メモリにありました)。パフォーマンスはコード内にあります。優れたパフォーマンスは優れたコードにあります。

すべてのコードが「ちょうど」16倍速く実行されたと想像できますか?

行数が少ない場合や使用率が低い場合でも、不良またはパフォーマンスに問題のあるコードを正当化しないでください。そうした場合、CPUとディスクを十分に冷却するために、私が傾いていると非難された風車の1つを借りる必要があるかもしれません。;-)

「TALLY」という言葉の言葉

ええ…そうですね。意味的に言えば、集計表には「タリー」ではなく数字が含まれています。主題に関する私の元の記事(技術の元の記事ではなかったが、最初の記事でした)で、私はそれを「タリー」と呼びました。ループの代わりに「カウント」するために使用され、何かを「カウント」するために何かを「集計」します。;-)それをあなたが何と呼ぶか​​...ナンバーテーブル、タリーテーブル、シーケンステーブル、何でも。気にしません。私にとって、「タリー」はより完全な意味であり、優れた怠DBなDBAであるため、7文字ではなく5文字(2文字は同一)しか含まれておらず、ほとんどの人にとっては簡単です。また、テーブルの命名規則に従っている「単数形」です。;-) それ' また、60年代の本のページを含む記事がそれを呼んだもの。私は常に「集計表」と呼んでいますが、私や他の誰かが何を意味するのかはまだ知っています。また、ペストのようなハンガリー記法は避けますが、関数を「fnTally」と呼び、「まあ、もしあなたが示したeff-en Tally関数を使用すれば、パフォーマンスの問題はないでしょう」と言うことができます。人事違反。;-) 実際にはHR違反ではありません。;-) 実際にはHR違反ではありません。;-)

私が心配しているのは、パフォーマンスに問題のあるrCTEや他の形式のHidden RBARに頼る代わりに、それを適切に使用することを学ぶ人々です。


2

必要なRIGHT JOIN1時間ごとに1つのレコードを返すクエリを使用してデータを取得する必要があります。

行番号を取得するいくつかの方法については、これを参照してください。現在の時刻から時間として減算することができます。

Oracleでは、デュアルの階層クエリにより行が生成されます。

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

私が問題を抱えているのは、「1時間ごとに1つのレコードを返すクエリ」です。過去12(または24)時間の1時間ごとに12(または24)レコードを生成する方法を見つけようとしています。
datagod
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.