まず、この投稿に返信しないように心からお詫び申し上げます。私はプリアンブルとしていくつかのコメントを書いた後、ちょうど "セージのアドバイス"の代わりに便利な答えを投稿することを完全に意図していました。そして実際の生活が起こったので、私はこの投稿を完全に失いました。
てみましょう最初の彼は、彼は彼がやった言ったように千回のイベントでそれを使用して移入されたと述べたテーブルを構築することにより、OPのポストを再訪。私は、WhileループまたはrCTEのいずれかのRBARの代わりに必要な "行の存在"を提供するために、高性能の "疑似カーソル"を使用して2015年と2016年のランダム開始日を使用してデータを少し近代化します(Recursive CTE )。サイドバーのビットとして
2005を使用している人々の全体の多くが残っていると、このための2008+技術を使用してもパフォーマンスの向上はありませんので、私は互換性のあるすべてのもの2005を保っています。
はここでテストテーブルを構築するためのコードです。詳細はコメントにあります。ここで
--====================================================================
-- Presets
--====================================================================
--===== Declare and prepopulate some obviously named variables
DECLARE @StartDate DATETIME
,@EndDate DATETIME
,@Days INT
,@Events INT
,@MaxEventGap INT
;
SELECT @StartDate = '2015-01-01' --Inclusive date
,@EndDate = '2017-01-01' --Exclusive date
,@Days = DATEDIFF(dd,@StartDate,@EndDate)
,@Events = 1000
,@MaxEventGap = 30 --Note that 1 day will be the next day
;
--====================================================================
-- Create the Test Table
--====================================================================
--===== If the test table already exists, drop it to make reruns of
-- this demo easier. I also use a Temp Table so that we don't
-- accidenttly screw up a real table.
IF OBJECT_ID('tempdb..#Events','U') IS NOT NULL
DROP TABLE #Events
;
--===== Build the test table.
-- I'm following what the OP did so that anyone with a case
-- sensitive server won't have a problem.
CREATE TABLE #Events
(
event_ID INT,
event_title NVARCHAR(50),
first_event_date DATETIME,
occurs_every INT
)
;
--====================================================================
-- Populate the Test Table
--====================================================================
--===== Build @Events number of events using the previously defined
-- start date and number of days as limits for the random dates.
-- To make life a little easier, I'm using a CTE with a
-- "pseudo-cursor" to form most of the data and then an
-- external INSERT so that I can name the event after the
-- event_ID.
WITH cteGenData AS
(
SELECT TOP (@Events)
event_ID = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
,first_event_date = DATEADD(dd, ABS(CHECKSUM(NEWID())) % @Days, @StartDate)
,occurs_every = ABS(CHECKSUM(NEWID())) % 30 + 1
FROM sys.all_columns ac1 --Has at least 4000 rows in it for most editions
CROSS JOIN sys.all_columns ac2 --Just in case it doesn't for Express ;-)
)
INSERT INTO #Events
(event_ID, event_title, first_event_date, occurs_every)
SELECT event_ID
,event_title = 'Event #' + CAST(event_id AS VARCHAR(10))
,first_event_date
,occurs_every
FROM cteGenData
;
--===== Let's see the first 10 rows
SELECT TOP 10 *
FROM #Events
ORDER BY event_ID
;
最初の10行がfirst_even_datetとoccurs_everyの値があるため、私は拘束されたランダムデータを生成するのに使用される方法ではかなり異なるものになることを理解した上でどのように見えるかです。
event_ID event_title first_event_date occurs_every
-------- ----------- ----------------------- ------------
1 Event #1 2016-10-12 00:00:00.000 10
2 Event #2 2015-04-25 00:00:00.000 28
3 Event #3 2015-11-08 00:00:00.000 4
4 Event #4 2016-02-16 00:00:00.000 25
5 Event #5 2016-06-11 00:00:00.000 15
6 Event #6 2016-04-29 00:00:00.000 14
7 Event #7 2016-04-16 00:00:00.000 9
8 Event #8 2015-03-29 00:00:00.000 2
9 Event #9 2016-02-14 00:00:00.000 29
10 Event #10 2016-01-23 00:00:00.000 8
OP実験を複製するには、必ずタリーテーブルが必要です。ここにそのコードがあります。既にある場合は、パフォーマンス上の理由から、一意のクラスタ化インデックス(通常はPKの形式)が必要であることを確認してください。私は廃止された "syscolumns"ビューを使用しないように、コードの "疑似カーソル"部分に行ソーステーブルを近代化しました。
--===== Create a Tally Table with enough sequential numbers
-- for more than 30 years worth of dates.
SELECT TOP 11000
IDENTITY(INT,1,1) AS N
INTO dbo.Tally
FROM sys.all_columns sc1
CROSS JOIN sys.all_columns sc2
;
--===== Add the quintessential Unique Clustered Index as the PK.
ALTER TABLE dbo.Tally
ADD CONSTRAINT PK_Tally_N
PRIMARY KEY CLUSTERED (N) WITH FILLFACTOR = 100
;
私たちは準備ができています。 OPのコードの一部がフォーラムで飲み込まれましたが、元の投稿の編集を使用して回復できました。私が作成したばかりのデータと一致するように「終了日」を変更したことを除いて、実際にはこのように見えます(それが私が作成した唯一の変更です)。コードにはスカラーまたは複数ステートメントのUDFが含まれていないため、統計情報を有効にして何が起こっているのか説明しようとしました。
ここで言及した変更でOPのコードです。
SET STATISTICS TIME,IO ON
;
SELECT event_id,
event_title,
first_event_date,
DATEADD(dd, occurs_every * (t.N - 1), [first_event_date]) AS Occurrence
FROM #Events
CROSS JOIN dbo.Tally t
WHERE t.N <= DATEDIFF(dd,first_event_date,'2017-03-01')/occurs_every + 1
ORDER BY Occurrence
;
SET STATISTICS TIME,IO OFF
;
ここにOPコードを実行した際の統計情報があります。すべてのスクロールについては申し訳ありませんが、長い行です。
(61766 row(s) affected)
Table 'Worktable'. Scan count 4, logical reads 118440, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Tally'. Scan count 4, logical reads 80, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table '#Events_____________________________________________________________________________________________________________00000000001F'. Scan count 5, logical reads 7, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, 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 = 4196 ms, elapsed time = 1751 ms.
明らかに、このパフォーマンスは、While LoopまたはrCTEでさえ打ち消すことができる吸い上げ音を作り出しています。何が問題ですか?
以下の実行計画で強調表示されている矢印をチェックすると、非SARGable(SARG = "Search ARGument"およびSARGableでないために1100万の実際の行が含まれています。 11,000行の集計表と1,000行の#Events表の間で完全なCROSS JOINが発生したことを示しています。そしてそれらは実績のある行であり、推測された行ではありません。
タリー表の「N」の欄は、式で使用され、全体タリー表#Eventsテーブルのすべての行の結果としてスキャンされなければならないためです。これは、Tally Tablesが遅いコードを生成するという一般的なエラーです。
だから、どのように我々はそれを修正しますか?各行の日付を計算するためにt.Nを使用するのではなく、日付の差をとって日数で割って、t.Nと何が起こるかを見分けるために必要な出現回数を把握しましょう。以下のコードで変更したのは、WHERE句の基準で、tをルックアップすることだけでした。N SARGable(インデックスを使用してシークを開始および停止し、その後に範囲スキャンを実行できる)
SET STATISTICS TIME,IO ON
;
SELECT event_id,
event_title,
first_event_date,
DATEADD(dd, occurs_every * (t.N - 1), [first_event_date]) AS Occurrence
FROM #Events
CROSS JOIN dbo.Tally t
WHERE t.N <= DATEDIFF(dd,first_event_date,'2017-03-01')/occurs_every + 1
ORDER BY Occurrence
;
SET STATISTICS TIME,IO OFF
;
新しい実行プランの外観は次のとおりです。 61,766行の実際の行(すべてキャッシュ内)は、1億1,000万行とはまったく異なります。
ここでは、計算上の天の小さなスライスの統計はどのように見えますか?
(61766 row(s) affected)
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table '#Events_____________________________________________________________________________________________________________00000000001F'. Scan count 5, logical reads 7, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Tally'. Scan count 1000, logical reads 3011, 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 = 78 ms, elapsed time = 528 ms.
- CPU時間は52.79倍または5279パーセント減少しました。
- 経過時間が2.32倍または232%減少しました。
- 合計は38.27倍または3827パーセント変更コードの
合計... WHERE句の1行減少読み込みます。
Itzik Ben-GanのインラインカスケーディングCTE(rCTEではありません)のいずれかを使用すると、読み込みの合計数をわずか7に減らすことができます。
結論は、タリーテーブルの使用はパフォーマンスの万能薬ですが、他のものと同様に正しく使用する必要があることです。 SARGable WHERE句を書くなど、ベストプラクティスを使用して、インデックスを正しく取得する必要があります。
また、私はこのことで非常に後悔しています。私はそれが将来誰かを助けることを願っています。私はまた、このスレッドのrCTEの例を書き直して、どれほど悪いのかを示す時間がないことをお詫びします。あなたがrCTEsがとても悪く、SQLServerCentral.comのメンバーシップを気にしない理由に興味があるなら、ここにその件に関する記事があります。私はここにすべてを掲示するつもりだが、そうするには時間がかかりすぎる。
Hidden RBAR: Counting with Recursive CTE's
どのデータベースエンジンを使用していますか?どのバージョンですか? – Lamak
SQL Server 2008. – Ethan