質問の例では、まったく同じ結果が得られません(OFFSET
例には1つずれたエラーがあります)。以下の更新されたフォームはその問題を修正し、ROW_NUMBER
ケースの余分な並べ替えを削除し、変数を使用してソリューションをより一般的にします。
DECLARE
@PageSize bigint = 10,
@PageNumber integer = 3;
WITH Numbered AS
(
SELECT TOP ((@PageNumber + 1) * @PageSize)
o.*,
rn = ROW_NUMBER() OVER (
ORDER BY o.[object_id])
FROM #objects AS o
ORDER BY
o.[object_id]
)
SELECT
x.name,
x.[object_id],
x.principal_id,
x.[schema_id],
x.parent_object_id,
x.[type],
x.type_desc,
x.create_date,
x.modify_date,
x.is_ms_shipped,
x.is_published,
x.is_schema_published
FROM Numbered AS x
WHERE
x.rn >= @PageNumber * @PageSize
AND x.rn < ((@PageNumber + 1) * @PageSize)
ORDER BY
x.[object_id];
SELECT
o.name,
o.[object_id],
o.principal_id,
o.[schema_id],
o.parent_object_id,
o.[type],
o.type_desc,
o.create_date,
o.modify_date,
o.is_ms_shipped,
o.is_published,
o.is_schema_published
FROM #objects AS o
ORDER BY
o.[object_id]
OFFSET @PageNumber * @PageSize - 1 ROWS
FETCH NEXT @PageSize ROWS ONLY;
ROW_NUMBER
計画では、推定コストがある0.0197935を:
OFFSET
計画では、推定コストがある0.0196955を:
これにより、推定コスト単位0.000098が節約されます(ただし、OFFSET
各行の行番号を返す場合、計画では追加の演算子が必要になります)。OFFSET
計画はまだ、一般的に言えば少し安くなりますが、推定コストはまさにそれであることを覚えていますか-本当のテストがまだ必要とされます。両方のプランのコストの大部分は、すべての入力セットのコストであるため、有用なインデックスは両方のソリューションに役立ちます。
定数リテラル値が使用される場合(OFFSET 30
元の例など)、オプティマイザーは、完全なソートの後にTopが続く代わりにTopN Sortを使用できます。TopNソートで必要な行が定数リテラルで<= 100(OFFSET
およびの合計FETCH
)である場合、実行エンジンは一般的なTopNソートよりも高速に実行できる別のソートアルゴリズムを使用できます。3つのケースはすべて、全体的に異なるパフォーマンス特性を持っています。
オプティマイザーがROW_NUMBER
使用するようOFFSET
に構文パターンを自動的に変換しない理由については、いくつかの理由があります。
- 既存のすべての用途に一致する変換を記述することはほとんど不可能です
- 一部のページングクエリが自動的に変換され、他のクエリは自動的に変換されない場合がある
OFFSET
計画はすべてのケースで改善することを保証するものではありません
上記の3番目のポイントの1つの例は、ページングセットが非常に広い場合に発生します。またはでインデックスをスキャンするよりも、非クラスター化インデックスを使用して必要なキーを検索し、クラスター化インデックスを手動で検索する方がはるかに効率的です。ページングアプリケーションが合計で行またはページの数を知る必要があるかどうかを考慮する追加の問題があります。「キーシーク」および「オフセット」メソッドの相対的なメリットについては、別の良い説明があります。OFFSET
ROW_NUMBER
全体として、OFFSET
十分なテストを行った後、適切であれば、使用するようにページングクエリを変更するという十分な情報に基づいて決定する方がよいでしょう。