OFFSET / FETCH NEXTから行の総数を取得する


90

だから、私は自分のウェブサイトにページングを実装したいレコードの数を返す関数を持っています。これを実現するために、SQL Server 2012のオフセット/フェッチ次を使用することが推奨されました。私たちのウェブサイトには、レコードの総数とその時点でどのページを表示しているかを示すエリアがあります。

以前は、レコードセット全体を取得しており、プログラムでページングを構築することができました。しかし、FETCH NEXT X ROWS ONLYでSQLを使用すると、返されるのはX行だけなので、合計レコードセットが何であるか、および最小ページと最大ページを計算する方法がわかりません。これを行うには、関数を2回呼び出し、最初の行で行数をカウントし、次に2番目の行をFETCH NEXTで実行するしかありません。クエリを2回実行しないより良い方法はありますか?パフォーマンスを落とすのではなく、スピードを上げることを目指しています。

回答:


112

使用することができますCOUNT(*) OVER()...ここで使用する簡単な例sys.all_objectsです:

DECLARE 
  @PageSize INT = 10, 
  @PageNum  INT = 1;

SELECT 
  name, object_id, 
  overall_count = COUNT(*) OVER()
FROM sys.all_objects
ORDER BY name
  OFFSET (@PageNum-1)*@PageSize ROWS
  FETCH NEXT @PageSize ROWS ONLY;

ただし、これは小さなデータセット用に予約する必要があります。より大きなセットでは、パフォーマンスが極端になる可能性があります。インデックス付きビューの維持(結果がフィルターされていないか、事前に句がわかっている場合にのみ機能します)やトリックの使用など、より優れた代替策については、このポールホワイトの記事を参照してくださいWHEREROW_NUMBER()


44
3,500,000レコードのテーブルでは、COUNT(*)OVER()に1分3秒かかりました。James Mobergが以下に説明するアプローチでは、同じデータセットを取得するのに13秒かかりました。カウントオーバーのアプローチは、小さいデータセットでも問題なく機能すると確信していますが、実際に大きくなり始めると、かなり遅くなります。
matthew_360 2014年

それとも、ただそれがテーブルから実際のデータを読む必要はありませんので、helluvalot高速であるCOUNT(1)OVER()は、数のように(*)し使用することができます
LDX

1
@AaronBertrandほんと?つまり、すべての列を含むインデックスがあるか、2008R2以降、これが大幅に改善されていることになります。そのバージョンでは、count(*)は順番に機能します。つまり、最初に*(すべての列のように)が選択され、次にカウントされます。count(1)を実行した場合は、定数を選択するだけです。これは、実際のデータを読み取るよりもはるかに高速です。
ldx

5
@idxいいえ、それは2008 R2でもうまくいきませんでした。私は6.5からSQL Serverを使用しており、COUNT(*)とCOUNT(1)の両方の最も狭いインデックスをスキャンするだけのエンジンがなかった時代を思い出しません。確かに2000年以降ではありません。でも、2008 R2のインスタンスがあります。SQLfiddleで再現すると、この違いが存在すると主張していることを実証できますか?試してよかったです。
Aaron Bertrand

2
SQLサーバー2016データベースで、約2,500万行のテーブルを検索し、約3000件を超えるページング(テーブル値関数への結合を含む)を実行すると、ミリ秒かかりました-すごい!
jkerak

139

COUNT()OVER()メソッドを使用すると、パフォーマンスの問題が発生しました(10レコードを返すのに40秒かかり、その後問題がなかったので、それがサーバーであったかどうかはわかりません。)この手法は、COUNT()OVER()を使用せずにすべての条件で機能し、同じこと:

DECLARE 
    @PageSize INT = 10, 
    @PageNum  INT = 1;

WITH TempResult AS(
    SELECT ID, Name
    FROM Table
), TempCount AS (
    SELECT COUNT(*) AS MaxRows FROM TempResult
)
SELECT *
FROM TempResult, TempCount
ORDER BY TempResult.Name
    OFFSET (@PageNum-1)*@PageSize ROWS
    FETCH NEXT @PageSize ROWS ONLY

31
COUNT(*)値を変数に保存する可能性があれば、それは本当に素晴らしいでしょう。ストアドプロシージャのOUTPUTパラメータとして設定できます。何か案は?
Kaへ2014年

1
別のテーブルでカウントを取得する方法はありますか?"TempResult"は最初の先行するSELECTステートメントでしか使用できないようです。
matthew_360 2014年

4
なぜこれがうまく機能するのですか?最初のCTEでは、すべての行が選択され、フェッチによって削減されます。最初のCTEですべての行を選択すると、処理速度が大幅に低下すると思いました。とにかく、これをありがとう!
jbd

1
私の場合、COUNT(1)OVER()よりも遅くなりました。おそらく、selectの関数が原因です。
Tiju John

1
これは、行が数百万の場合、時間がかかりすぎる小規模なデータベースに最適です。
Kiya

1

James Mobergの回答に基づいて:

Row_Number()SQL Server 2012がなく、OFFSETを使用できない場合、これはを使用した代替方法です。

DECLARE 
    @PageNumEnd INT = 10, 
    @PageNum  INT = 1;

WITH TempResult AS(
    SELECT ID, NAME
    FROM Tabla
), TempCount AS (
    SELECT COUNT(*) AS MaxRows FROM TempResult
)

select * 
from
(
    SELECT
     ROW_NUMBER() OVER ( ORDER BY PolizaId DESC) AS 'NumeroRenglon', 
     MaxRows, 
     ID,
     Name
    FROM TempResult, TempCount

)resultados
WHERE   NumeroRenglon >= @PageNum
    AND NumeroRenglon <= @PageNumEnd
ORDER BY NumeroRenglon
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.