web-dev-qa-db-ja.com

クエリのレコード数を事前に確認する

SQL Server 2012でのクエリの結果として、ExtJSでレポートを作成します。

ページングでExt.Gridを使用しているため、オフセットを使用して、クエリが返すレコードの量を制限できます。ただし、このExtJS機能が機能するためには、指定したクエリが持つレコードの合計量を提供する必要があります。

一部のレポートは、日中にフィルターとデータ変更を使用します。これは、クエリを実行することによってのみ合計数を知ることができるためです。その結果、2回実行されます。1回はcount(*)で合計数を取得し、もう1回は適切なデータを取得します。

現在実行しているように2回実行する必要がなく、すべてのレコードをループすることなく、offsetコマンドがある場合でも、クエリの合計数を見つける方法はありますか?

3
Hikari

残念ながら、クエリが完了するまで、レコードの総数はわかりません。これは、何とかして無制限のクエリを終了させ、その後行のサブセットを取得する必要があることを意味します。

これを行う1つの方法は、COUNT(*) OVER () AS [TotalRows]をSELECTリストに追加することです。これは、すべての行に対して同じ値が繰り返される列として値を返しますが、それでも変わりません。

_SELECT [object_id], [schema_id], [name], [type_desc], COUNT(*) OVER () AS [TotalRows]
FROM sys.objects
ORDER BY [object_id] ASC OFFSET 10 ROWS FETCH NEXT 7 ROWS ONLY;
_

もちろん、それは自由に利用できる値にアクセスするだけではありません。上記のクエリの実行プランを、COUNT(*)..式なしの同じもののプランと比較するだけです。

_SELECT [object_id], [schema_id], [name], [type_desc]
FROM sys.objects
ORDER BY [object_id] ASC OFFSET 10 ROWS FETCH NEXT 7 ROWS ONLY;
_

私が思いついた別の方法は、Stack Overflowでほぼ同じ質問に対する私の回答で詳細に説明されています。

TSQL:返される行を制限し、制限なしで(すべての行に追加せずに)返される合計数をカウントする方法はありますか?

その質問とこの質問の違いは、他の質問は、カウントしている結果セットの列として値を具体的に返さないことです。また、もう1つの質問は「制限」の側面に関するものであり、この質問は両方ではないにしても少なくとも「オフセット」に関するものです。しかし、「オフセット」の部分を追加することは難しくありません。次のようなことを行うには、目的の結果を返すループの前にループを追加するだけです。

_// assume an input param or variable of: int Offset;

int _RowCounter = 0;
while (Reader.Read() && ++_RowCounter < Offset);
_

おもしろいことに、クエリを実行して行を取得せずに行数を取得できる別のメカニズムを試してみると思いました:CURSOR(はい、EVILCURSOR)。変数_@@CURSOR_ROWS_は、行をOPENしたことがない場合でも、FETCHを呼び出した後に入力されます。

以下では(どれだけうまく機能するかはわかりませんが)最初の行をフェッチするために循環する必要がないので、_FETCH ABSOLUTE x_を使用して "オフセット"アスペクトを処理します。ただし、ABSOLUTEを使用すると、_FAST_FORWARD_や_FORWARD_ONLY_などのオプションを使用できなくなります。 _FAST_FORWARD_を使用できないため、次にSTATICを使用しましたが、STATICの追加が実際に役立つか害を及ぼすかどうかをテストする必要があります。

今、望ましい結果を得る唯一の方法は、それらをテーブル変数に格納することです(または一時テーブルは機能しますが、この場合はテーブル変数の方が良いと思います)。これにより、単一の結果セットとして返すことができます。 FETCH句なしでINTOを実行すると、呼び出しごとに異なる結果セットが返されます(おそらく望ましくない)。

_DECLARE @Offset INT = 10,
        @Limit INT = 7;

DECLARE cursed CURSOR LOCAL READ_ONLY STATIC -- can't use FAST_FORWARD or FORWARD_ONLY :(
FOR  SELECT so.[object_id], so.[schema_id], so.name, so.type_desc
     FROM   sys.objects so
     ORDER BY so.[object_id] ASC;

DECLARE @object_id INT,
        @schema_id INT,
        @name sysname,
        @type_desc NVARCHAR(60);

DECLARE @RowCounter INT = 0,
        @StartRow INT = (@Offset + 1); -- Offset is how many rows to skip

DECLARE @Results TABLE ([object_id] INT NOT NULL, [schema_id] INT NOT NULL,
                        [name] sysname NOT NULL, [type_desc] NVARCHAR(60) NOT NULL);

OPEN cursed; -- execute the query

SELECT @@CURSOR_ROWS AS [RowCountBeforeRetrievingAnyRows];

FETCH ABSOLUTE @StartRow -- position cursor at beginning of desired range
FROM  cursed
INTO  @object_id, @schema_id, @name, @type_desc;

WHILE (@@FETCH_STATUS = 0 AND @RowCounter < @Limit)
BEGIN
  INSERT INTO @Results ([object_id], [schema_id], [name], [type_desc])
  VALUES (@object_id, @schema_id, @name, @type_desc);

  SET @RowCounter += 1;

  FETCH NEXT
  FROM  cursed
  INTO  @object_id, @schema_id, @name, @type_desc;
END;

CLOSE cursed;
DEALLOCATE cursed;

-- return desired range of rows
SELECT * FROM @Results;


-----------------------------------------------
-- check the actual data to see if it worked
SELECT [object_id], [schema_id], [name], [type_desc], COUNT(*) OVER () AS [TotalRows]
FROM sys.objects
ORDER BY [object_id] ASC OFFSET @Offset ROWS FETCH NEXT @Limit ROWS ONLY;
_

_STATIC CURSOR_が結果をtempdbに保存することを考えると、以下は基本的に操作上は同じですが、少し直接的です。

_DECLARE @Offset INT = 10,
        @Limit INT = 7;

DECLARE @Results TABLE ([object_id] INT NOT NULL, [schema_id] INT NOT NULL,
                        [name] sysname NOT NULL, [type_desc] NVARCHAR(60) NOT NULL,
                        [RowID] INT NOT NULL IDENTITY(1, 1) PRIMARY KEY);

INSERT INTO @Results ([object_id], [schema_id], [name], [type_desc])
  SELECT so.[object_id], so.[schema_id], so.name, so.type_desc
  FROM   sys.objects so
  ORDER BY so.[object_id] ASC;

SELECT @@ROWCOUNT AS [TotalRows];

-- return desired range via OFFSET / FETCH (Clustered Index Scan)
SELECT [object_id], [schema_id], [name], [type_desc]
FROM @Results
ORDER BY [RowID] ASC OFFSET @Offset ROWS FETCH NEXT @Limit ROWS ONLY;

-- return desired range via WHERE (Clustered Index Seek)
SELECT [object_id], [schema_id], [name], [type_desc]
FROM @Results
WHERE [RowID] > @Offset
AND   [RowID] <= (@Offset + @Limit)
ORDER BY [RowID] ASC;


-----------------------------------------------
-- check the actual data to see if it worked. OR, maybe this is actually better?
SELECT [object_id], [schema_id], [name], [type_desc], COUNT(*) OVER () AS [TotalRows]
FROM sys.objects
ORDER BY [object_id] ASC OFFSET @Offset ROWS FETCH NEXT @Limit ROWS ONLY;
_
4
Solomon Rutzky