この質問は、ここで提示された優れた質問からの離陸です。
これまでのキャストは検索可能ですが、それは良いアイデアですか?
私の場合、WHERE
句は関係ありませんが、タイプDATE
の列を持つイベントテーブルに参加することに関心があります。
1つのテーブルには_DATETIME2
_があり、もう1つのテーブルにはDATE
...があるため、CAST( AS DATE)
を使用してJOIN
を効果的に実行するか、「従来の」範囲クエリを使用できます(> =日付AND <日付+1)。
私の質問はどちらが望ましいですか? DATETIME
値は、述語DATE
値とほとんど一致しません。
私はDATETIME
を持つ2M行のオーダーにとどまり、DATE
を持つ5k行未満にとどまると予想します(この考慮事項が違いを生む場合)
JOIN
句を使用する場合と同じ動作をWHERE
でも期待できますか?スケーリングでパフォーマンスを維持したいのはどちらですか。 MSSQL 2012で答えは変わりますか?
私の一般的な使用例は、イベントテーブルをカレンダーテーブルのように扱うことです
_SELECT
events.columns
,SOME_AGGREGATIONS(tasks.column)
FROM
events
LEFT OUTER JOIN
tasks
--This appropriately states my intent clearer
ON CAST(tasks.datetimecolumn AS DATE) = events.datecolumn
--But is this more effective/scalable?
--ON tasks.datetimecolumn >= events.datecolumn
--AND tasks.datetimecolumn < DATEADD(day,1,events.datecolumn)
GROUP BY
events.columns
_
"場合によります"。
_=
_述語とこれまでのcast
の利点の1つは、結合をハッシュまたはマージできることです。範囲バージョンは、ネストされたループ計画を強制します。
datetimecolumn
のtasks
にシークするのに役立つインデックスがない場合、これは大きな違いをもたらします。
質問で言及された5K/200万行のテストデータの設定
_CREATE TABLE events
(
eventId INT IDENTITY PRIMARY KEY,
datecolumn DATE NOT NULL,
details CHAR(1000) DEFAULT 'D'
)
INSERT INTO events
(datecolumn)
SELECT TOP 5000 DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY @@SPID), GETDATE())
FROM spt_values v1,
spt_values v2
CREATE TABLE tasks
(
taskId INT IDENTITY PRIMARY KEY,
datetimecolumn DATETIME2 NOT NULL,
details CHAR(1000) DEFAULT 'D'
);
WITH N
AS (SELECT number
FROM spt_values
WHERE number BETWEEN 1 AND 40
AND type = 'P')
INSERT INTO tasks
(datetimecolumn)
SELECT DATEADD(MINUTE, number, CAST(datecolumn AS DATETIME2))
FROM events,
N
_
次に、電源を入れます
_SET STATISTICS IO ON;
SET STATISTICS TIME ON;
_
CAST
バージョンを試す
_SELECT events.eventId,
MAX(tasks.details)
FROM events
LEFT OUTER JOIN tasks
ON CAST(tasks.datetimecolumn AS DATE) = events.datecolumn
GROUP BY events.eventId
_
7.4秒で完了
_Table 'Worktable'. Scan count 0, logical reads 0
Table 'tasks'. Scan count 1, logical reads 28679
Table 'events'. Scan count 1, logical reads 719
CPU time = 3042 ms, elapsed time = 7434 ms.
_
結合から_GROUP BY
_に入る行の推定数は小さすぎ(5006.27と実際の2,000,000)、ハッシュ集計はtempdb
に溢れました
範囲述語を試す
_SELECT events.eventId,
MAX(tasks.details)
FROM events
LEFT OUTER JOIN tasks
ON tasks.datetimecolumn >= events.datecolumn
AND tasks.datetimecolumn < DATEADD(day, 1, events.datecolumn)
GROUP BY events.eventId
_
等価述語の欠如は、ネストされたループ計画を強制します。このクエリをサポートするのに役立つインデックスはないため、200万行のテーブルを5,000回スキャンする以外に選択肢はありません。
私のマシンでは、最終的に1分40秒後に完了した並列プランを提供しました。
_Table 'tasks'. Scan count 4, logical reads 143390000
Table 'events'. Scan count 5, logical reads 788
Table 'Worktable'. Scan count 0, logical reads 0
CPU time = 368193 ms, elapsed time = 100528 ms.
_
今回は、結合からアグリゲートに入る行の数が大幅にover推定されました(推定124,939,000対実際の2,000,000)
テーブルを変更してからそれぞれの日付/時刻列をクラスター化された主キーにした後、実験を繰り返して結果を変更しました。
どちらのクエリでも、ネストされたループプランが選択されました。 CAST
as DATE
バージョンは、4.5秒で完了したシリアルバージョンと、CPU時間3.2秒で経過時間1.1秒で完了したパラレルプランを範囲バージョンに提供しました。
2番目のクエリに_MAXDOP 1
_を適用して図をより簡単に比較できるようにすると、次の結果が返されます。
クエリ1
_Table 'Worktable'. Scan count 0, logical reads 0
Table 'tasks'. Scan count 5000, logical reads 78137
Table 'events'. Scan count 1, logical reads 719
CPU time = 3167 ms, elapsed time = 4497 ms.
_
クエリ2
_Table 'tasks'. Scan count 5000, logical reads 49440
Table 'events'. Scan count 1, logical reads 719
CPU time = 3042 ms, elapsed time = 3147 ms.
_
クエリ1では、結合から推定された5006.73行があり、ハッシュ集計がtempdb
に再度あふれました。
クエリ2にも大きな過大評価があります(今回は120,927,000)。
2つの結果のもう1つの明らかな違いは、範囲クエリが何らかの方法でtasks
をより効率的にシークできるように見えることです。 _49,440
_ページと_78,137
_の読み取りのみ。
日付バージョンとしてのキャストが求める範囲は、内部関数GetRangeThroughConvert
から導出されます。計画はCONVERT(date,[dbo].[tasks].[datetimecolumn],0)= [dbo].[events].[datecolumn]
の残余述語を示しています。
クエリ2が次のように変更された場合
_ LEFT OUTER JOIN tasks
ON tasks.datetimecolumn > DATEADD(day, -1, events.datecolumn)
AND tasks.datetimecolumn < DATEADD(day, 1, events.datecolumn)
_
その後、読み取りの数は同じになります。 _CAST AS DATE
_バージョンで使用される動的シークは、不要な行(1日ではなく2日)を読み取り、残りの述語でそれらを破棄します。
もう1つの可能性は、date
およびtime
コンポーネントを異なる列に格納するようにテーブルを再構成することです。
_CREATE TABLE [dbo].[tasks](
[taskId] [int] IDENTITY(1,1) NOT NULL,
[datecolumn] date NOT NULL,
[timecolumn] time NOT NULL,
[datetimecolumn] AS DATEADD(day, DATEDIFF(DAY,0,[datecolumn]), CAST([timecolumn] AS DATETIME2(7))),
[details] [char](1000) NULL,
PRIMARY KEY CLUSTERED
(
[datecolumn] ASC,
[timecolumn] ASC
))
_
datetimecolumn
は コンポーネントパーツから派生 にすることができ、これは行サイズに影響を与えません(date
+ time(n)
の幅は同じであるため) datetime2(n)
)の幅として。 (例外として、追加の列が_NULL_BITMAP
_のサイズを増やす場合)
クエリは単純な_=
_述語になります
_SELECT events.eventId,
MAX(tasks.details)
FROM events
LEFT OUTER JOIN tasks
ON tasks.datecolumn = events.datecolumn
GROUP BY events.eventId
_
これにより、並べ替える必要なく、テーブル間のマージ結合が可能になります。これらのテーブルサイズでは、ネストされたループ結合がとにかく以下のように統計で選択されています。
_Table 'tasks'. Scan count 5000, logical reads 44285
Table 'events'. Scan count 1, logical reads 717
CPU time = 2980 ms, elapsed time = 3012 ms.
_
また、先頭のインデックス列としてdate
を個別に格納するさまざまな論理結合タイプを許可することも、日付によるグループ化など、tasks
に対する他のクエリに利益をもたらす可能性があります。
なぜ_=
_述語は、同じネストされたループプラン(_> <=
_ vs _44,285
_)の_49,440
_バージョンよりもtasks
での論理読み取りが少ないことを示します先読みメカニズムに関連します。
トレースフラグ_652
_をオンにすると、範囲バージョンの論理読み取りが等価バージョンの論理読み取りと同じになります。
カーディナリティの見積もりは、このアプローチと日付範囲アプローチのどちらを使用する場合でも潜在的に影響を受ける可能性があるというマーティンに同意します。また、_CONVERT(DATE
_を使用し、それでもsargabilityを取得すると、他の人々がコードを読んだり、コードから学習したりする可能性があるため、特に列がインデックス付けされている場合は、列に対して関数を使用することをお勧めします。これはonly例外であり、他のすべてのケースでは、シークが可能だった可能性があるときに実際にスキャンを強制するため、例外を使用することはお勧めしませんコードの作成者以外に実際の利点はありません。それは短命です。より簡潔な式を書くために数秒節約でき、それは一度行うことです。私は質問に答えるときいつも同じ反対に直面します-長さなしでvarchar
を宣言するなど、悪い習慣を含む回答を投稿している他の人を見かけ、よくコメントします。私が聞いた言い訳は、それがthisの場合にうまく機能するということですが、それはポイントではありません-人々はこのケースから学び、他のケースにそれを適用します。また、同じケースで壊れる可能性もあります。たとえば、後で数週間または半日などで参加したい場合、別のデータ型を使用する必要があり、得ていたと思っていた利点を失う可能性があることを想像してください。
INNER JOINの場合、WHERE句とON句を使用しても違いはありません。ただし、同様に、条件をON句で結合し、条件をWHERE句でフィルタリングし続けます。もちろん、OUTER JOINについて話している場合は、特定の基準の配置がセマンティクスを変更する可能性があるため、これは変更されます。
私はこのようにクエリを記述します(そして、ほとんどの人から面白い視線を得る可能性があるため、作り上げた「Word」パフォーマンスを使用する場所には注意します)。
_LEFT OUTER JOIN
dbo.tasks -- always use schema prefix!
ON tasks.datetimecolumn >= events.datecolumn
AND tasks.datetimecolumn < DATEADD(DAY, 1, events.datecolumn)
_
もちろん、これをテストして、DATEADD()
がイベントテーブルに与える影響を確認する必要があります。それはテーブルが大きく、マージンが小さいように聞こえるので、効果が大きくなることはないと思いますが、チェックしても問題はありません。