このドキュメント
は、SQL Server で実行中の合計を計算するための優れたリソースです。 OVER
を取得するためのキャンペーンの一環として SQL Server チームに提出された Itzik Ben Gan による 節は、最初の SQL Server 2005 実装からさらに拡張されました。その中で彼は、何万行ものカーソルを取得すると、セットベースのソリューションを実行する方法を示しています。 SQL Server 2012 は確かに OVER
を拡張しました 句を使用すると、この種のクエリがはるかに簡単になります。
SELECT col1,
SUM(col1) OVER (ORDER BY ind ROWS UNBOUNDED PRECEDING)
FROM @tmp
ただし、SQL Server 2005 を使用しているため、これは利用できません。
Adam Machanic ここに表示 CLR を使用して標準の TSQL カーソルのパフォーマンスを向上させる方法。
このテーブル定義について
CREATE TABLE RunningTotals
(
ind int identity(1,1) primary key,
col1 int
)
ALLOW_SNAPSHOT_ISOLATION ON
を使用して、データベースに 2,000 行と 10,000 行の両方を持つテーブルを作成します この設定がオフになっているもの (この理由は、最初の結果が設定がオンの DB にあり、結果の不可解な側面につながったためです)。
すべてのテーブルのクラスター化インデックスには、1 つのルート ページしかありませんでした。それぞれのリーフ ページ数は次のとおりです。
+-------------------------------+-----------+------------+
| | 2,000 row | 10,000 row |
+-------------------------------+-----------+------------+
| ALLOW_SNAPSHOT_ISOLATION OFF | 5 | 22 |
| ALLOW_SNAPSHOT_ISOLATION ON | 8 | 39 |
+-------------------------------+-----------+------------+
次のケースをテストしました (リンクは実行計画を示します)
<オール>
追加の CTE オプションを含める理由は、ind
が 列が連続しているとは限りません。
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
DECLARE @col1 int, @sumcol1 bigint;
WITH RecursiveCTE
AS (
SELECT TOP 1 ind, col1, CAST(col1 AS BIGINT) AS Total
FROM RunningTotals
ORDER BY ind
UNION ALL
SELECT R.ind, R.col1, R.Total
FROM (
SELECT T.*,
T.col1 + Total AS Total,
rn = ROW_NUMBER() OVER (ORDER BY T.ind)
FROM RunningTotals T
JOIN RecursiveCTE R
ON R.ind < T.ind
) R
WHERE R.rn = 1
)
SELECT @col1 =col1, @sumcol1=Total
FROM RecursiveCTE
OPTION (MAXRECURSION 0);
すべてのクエリに CAST(col1 AS BIGINT)
がありました 実行時のオーバーフロー エラーを回避するために追加されました。さらに、それらすべてについて、上記のように結果を変数に割り当てて、考慮から結果を送り返すのに費やす時間をなくしました。
結果
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| | | | Base Table | Work Table | Time |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| | Snapshot | Rows | Scan count | logical reads | Scan count | logical reads | cpu | elapsed |
| Group By | On | 2,000 | 2001 | 12709 | | | 1469 | 1250 |
| | On | 10,000 | 10001 | 216678 | | | 30906 | 30963 |
| | Off | 2,000 | 2001 | 9251 | | | 1140 | 1160 |
| | Off | 10,000 | 10001 | 130089 | | | 29906 | 28306 |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| Sub Query | On | 2,000 | 2001 | 12709 | | | 844 | 823 |
| | On | 10,000 | 2 | 82 | 10000 | 165025 | 24672 | 24535 |
| | Off | 2,000 | 2001 | 9251 | | | 766 | 999 |
| | Off | 10,000 | 2 | 48 | 10000 | 165025 | 25188 | 23880 |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| CTE No Gaps | On | 2,000 | 0 | 4002 | 2 | 12001 | 78 | 101 |
| | On | 10,000 | 0 | 20002 | 2 | 60001 | 344 | 342 |
| | Off | 2,000 | 0 | 4002 | 2 | 12001 | 62 | 253 |
| | Off | 10,000 | 0 | 20002 | 2 | 60001 | 281 | 326 |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| CTE Alllows Gaps | On | 2,000 | 2001 | 4009 | 2 | 12001 | 47 | 75 |
| | On | 10,000 | 10001 | 20040 | 2 | 60001 | 312 | 413 |
| | Off | 2,000 | 2001 | 4006 | 2 | 12001 | 94 | 90 |
| | Off | 10,000 | 10001 | 20023 | 2 | 60001 | 313 | 349 |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
相関サブクエリと GROUP BY
の両方 バージョンは、RunningTotals
でクラスター化されたインデックス スキャンによって駆動される「三角形」のネストされたループ結合を使用します テーブル (T1
) そして、そのスキャンによって返された行ごとに、テーブルをシークします (T2
) T2.ind<=T1.ind
での自己結合 .
これは、同じ行が繰り返し処理されることを意味します。 T1.ind=1000
の場合 行が処理され、自己結合が取得され、すべての行が ind <= 1000
で合計されます 、次に T1.ind=1001
の次の行 同じ 1000 行が再び取得されます 追加の 1 行などと一緒に合計されます。
2,000 行のテーブルに対するこのような操作の総数は 2,001,000 で、10,000 行の場合は 50,005,000 またはそれ以上です (n² + n) / 2
明らかに指数関数的に成長しています。
2,000 行の場合、GROUP BY
の主な違いは サブクエリのバージョンは、前者が結合後にストリーム集計を持っているため、それにフィードする 3 つの列があることです (T1.ind
, T2.col1
, T2.col1
) と GROUP BY
T1.ind
のプロパティ 一方、後者はスカラー集計として計算され、結合前のストリーム集計では T2.col1
しかありません それにフィードし、GROUP BY
はありません プロパティはまったく設定されていません。この単純な配置は、CPU 時間の削減という点で測定可能な利点があることがわかります。
10,000 行の場合、サブクエリ プランにさらに違いがあります。 熱心なスプール
を追加します すべての ind,cast(col1 as bigint)
をコピーします 値を tempdb
に .スナップショット分離がオンになっている場合、これはクラスター化されたインデックス構造よりもコンパクトに機能し、最終的な効果は読み取り数を約 25% 削減することです (ベース テーブルはバージョン管理情報用にかなりの空き領域を保持するため)。このオプションがオフの場合、コンパクトではなくなります (おそらく bigint
が原因です)。 vs int
違い)、より多くの読み取り結果が得られます。これにより、サブクエリとバージョンごとのグループの間のギャップが減少しますが、サブクエリが引き続き優先されます。
しかし、明らかな勝者は Recursive CTE でした。 「ギャップなし」バージョンでは、ベース テーブルからの論理読み取りは 2 x (n + 1)
になりました n
を反映 index は 2 レベルのインデックスをシークして、すべての行と、何も返さずに再帰を終了する最後の追加の行を取得します。それでも、22 ページのテーブルを処理するには 20,002 回の読み取りが必要です!
再帰 CTE バージョンの論理作業テーブルの読み取りが非常に多くなります。ソース行ごとに 6 回のワークテーブル読み取りでうまくいくようです。これらは、前の行の出力を格納し、次の反復で再度読み取られるインデックス スプールから取得されます (これについては、Umachandar Jayachandran こちら )。数が多いにもかかわらず、これは依然として最高のパフォーマーです。