web-dev-qa-db-ja.com

2回実行せずにCOUNT(*)個のサブクエリを選択する

ページ番号やその他の制限がある結果セットを返す手順があります。 OUTPUTパラメーターとして、ページ番号を除くパラメーターに従って、選択された行の合計量を返す必要があります。だから私はそのようなものを持っています:

WITH SelectedItems AS
(SELECT Id, Row1, Row2, ROW_NUMBER() OVER (ORDER BY Row1) AS Position
FROM Items
WHERE Row2 = @Row2)
SELECT Id, Row1, Row2
FROM SelectedItems
WHERE Position BETWEEN @From AND @To

次に、OUTPUTパラメーターを内部クエリの行数に設定する必要があります。クエリをコピーしてカウントするだけですが、このクエリは数千行を返す可能性があるため(将来的にはさらに増える可能性があります)、優れたパフォーマンスでそれを実行する方法を探しています。テーブル変数について考えていましたが、それは良い考えですか?または他の提案?

具体的には、Microsoft SQL Server 2008です。

ありがとう、Jan

19
Nidzo

COUNT(*)を使用して、メインクエリの行全体を個別の列としてカウントできます。このような:

WITH SelectedItems AS
(SELECT Id, Row1, Row2, ROW_NUMBER() OVER (ORDER BY Row1) AS Position, 
COUNT(*) OVER () AS TotalRows
FROM Items
WHERE Row2 = @Row2)
SELECT Id, Row1, Row2
FROM SelectedItems
WHERE Position BETWEEN @From AND @To

これは、出力パラメーターではなく結果セットにカウントを返しますが、要件に合うはずです。それ以外の場合は、一時テーブルと組み合わせます。

DECLARE @tmp TABLE (Id int, RowNum int, TotalRows int);

WITH SelectedItems AS
(SELECT Id, Row1, Row2, ROW_NUMBER() OVER (ORDER BY Row1) AS Position, 
COUNT(*) OVER () AS TotalRows
FROM Items
WHERE Row2 = @Row2)
INSERT @tmp
SELECT Id, Row1, Row2
FROM SelectedItems
WHERE Position BETWEEN @From AND @To

SELECT TOP 1 @TotalRows = TotalRows FROM @tmp
SELECT * FROM @tmp

ページングされた結果だけに一時テーブルを使用すると、メモリはそれほど使用されず(もちろんページサイズによって異なります)、短時間だけライブ状態を維持します。一時テーブルから完全な結果セットを選択し、TotalRowsを選択すると、ほんの少し時間がかかります。

これは、完全に別のクエリを実行するよりもはるかに高速です。テストでは(WITHを繰り返して)実行時間が2倍になりました。

19
badbod99

別のクエリで実行する必要があると思います。これら2つのクエリはほとんど同じように見えるかもしれませんが、クエリオプティマイザーがそれらを処理する方法はかなり大きく異なります。

理論的には、SQL Serverは、サブクエリのすべての行を数えてカウントできない場合もあります。

3
Mehrdad Afshari

完全な行数を取得するには、範囲を制限せずに、クエリ全体を少なくとも1回実行する必要があります。とにかくこれを行うので、すべての行に冗長なcount(*)列でデータリーダーをオーバーロードするのではなく、見つかった合計行を出力するために@@ RowCountを選択する必要があります。

1. NEWクエリを初めて実行する場合:

select YOUR_COLUMNS 
from YOUR_TABLE 
where YOUR_SEARCH_CONDITION 
order by YOUR_COLUMN_ORDERING_LIST;
select @@rowcount;

2.最初のX行のみを読み取る

上記のクエリは、SqlDataReaderへの呼び出しごとに送信される冗長なCOUNT(*)列でSqlDataReaderが溢れるのを防ぎます。 クエリを初めて実行しているので...範囲を選択する代わりに、最初のX行のみを読み取ります。これにより、必要なものが完全に得られます...完全な結果数、最初のXレコード、および冗長なカウント列のない結果セットの効率的なストリーミング。

3.以降のSAMEクエリの実行で結果のサブセットを取得する

select YOUR_COLUMNS 
from (select YOUR_COLUMNS, ROW_NUMBER() 
over(order by BY YOUR_COLUMN_ORDERING_LIST) as RowNum) Results 
where Results.RowNum between @From and @To;

とにかく、 @@rowcountは、結果セットを制限することなく(最初のX結果が必要です)、別のcount()クエリを実行せずに、一時テーブルを使用せずに、クエリの最初の実行でカウントにアクセスする最も直接的な方法です。冗長なcount()列を含みます。

2
Triynko

現在、コードベースにアクセスできませんが、COUNT()OVER(または同様のコマンド)を使用して、サブクエリの一部として行の総数を返すことができると思います。その後、それを最終結果セットの一部として返すことができます。それはすべての行で複製されますが、ページングを使用していて、いずれにしても最終結果が制限されているはずのアプリケーションについては、私の意見ではマイナーなパフォーマンスヒットです。

数時間で正確なコードを投稿します。

編集:これは、カウントを生成するために使用した行です。結局、開発者はそれ自体でカウントを取得する別のメソッドを望んでいたので、今では同じストアドプロシージャ内の2つの場所で検索条件を維持しています。

COUNT(*) OVER (PARTITION BY '') AS TotalCount

これをCTEに追加すると、TotalCountを選択でき、各行の列になります。

2
Tom H

出力変数を@@ RowCountに設定できませんでしたか?これにより、最後に実行されたステートメントの影響を受ける行が取得されます。

SELECT stuff FROM mytable

SET @output = @@ROWCOUNT

これで必要なものが得られ、クエリを再度実行する必要はありません。

1
SqlRyan