web-dev-qa-db-ja.com

SELECTステートメントの後に行カウントが必要:最適なSQLアプローチは何ですか?

単一のテーブル(結合なし)から列を選択しようとしていますが、理想的には行の取得を開始する前に、行数のカウントが必要です。必要な情報を提供する2つのアプローチに到達しました。

アプローチ1:

SELECT COUNT( my_table.my_col ) AS row_count
  FROM my_table
 WHERE my_table.foo = 'bar'

それから

SELECT my_table.my_col
  FROM my_table
 WHERE my_table.foo = 'bar'

またはアプローチ2

SELECT my_table.my_col, ( SELECT COUNT ( my_table.my_col )
                            FROM my_table
                           WHERE my_table.foo = 'bar' ) AS row_count
  FROM my_table
 WHERE my_table.foo = 'bar'

SQLドライバー(SQL Native Client 9.0)ではSELECTステートメントでSQLRowCountを使用できないため、これを行っていますが、情報を割り当てる前に配列を割り当てるために結果の行数を知る必要があります。残念ながら、動的に割り当てられたコンテナの使用は、私のプログラムのこの分野では選択肢ではありません。

次のシナリオが発生する可能性があることを心配しています。

  • カウントのSELECTが発生します
  • 行を追加または削除する別の命令が発生します
  • データのSELECTが発生し、突然配列のサイズが間違っています。
    -最悪の場合、これは配列の制限を超えてデータを書き込もうとし、プログラムをクラッシュさせます。

アプローチ2はこの問題を禁止していますか?

また、2つのアプローチのいずれかが高速になりますか?もしそうなら、どれ?

最後に、考慮する必要があるより良いアプローチがあります(おそらく、SQLRowCountを使用してSELECT結果の行数を返すようにドライバーに指示する方法ですか?)

尋ねた人のために、私はネイティブのC++と前述のSQLドライバー(Microsoft提供)を使用しています。

31
antik

COUNT(*)と実際のクエリで一貫した結果が得られることを100%確実にする方法は2つしかありません。

  • アプローチ2のように、COUNT(*)をクエリと組み合わせます。kogusのコメントに示されている相関サブクエリフォームではなく、例で示すフォームをお勧めします。
  • SNAPSHOTまたはSERIALIZABLE分離レベルでトランザクションを開始した後、アプローチ1のように2つのクエリを使用します。

これらの分離レベルのいずれかを使用することは重要です。他の分離レベルを使用すると、他のクライアントが作成した新しい行を現在のトランザクションで表示できるようになるためです。 SET TRANSACTION ISOLATION 詳細については。

16
Bill Karwin

SQL Serverを使用している場合、クエリ後に @@ RowCount 関数を選択できます(または、結果セットに20億行を超える可能性がある場合は RowCount_Big() を使用します=関数)。これは、前のステートメントで選択された行数、または挿入/更新/削除ステートメントの影響を受ける行数を返します。

SELECT my_table.my_col
  FROM my_table
 WHERE my_table.foo = 'bar'

SELECT @@Rowcount

または、アプローチ2と同様に送信された結果に行カウントを含める場合は、 OVER句 を使用できます。

SELECT my_table.my_col,
    count(*) OVER(PARTITION BY my_table.foo) AS 'Count'
  FROM my_table
 WHERE my_table.foo = 'bar'

OVER句を使用すると、サブクエリを使用して行数を取得するよりもパフォーマンスが大幅に向上します。 @@ RowCountステートメントを使用すると、select @@ RowCountステートメントのクエリコストが発生しないため、最高のパフォーマンスが得られます。

コメントへの応答で更新します。この例では、パーティション内の行数を示します-この場合は「PARTITION BY my_table.foo」で定義されています。各行の列の値は、my_table.fooと同じ値を持つ行の数です。サンプルクエリには句「WHERE my_table.foo = 'bar'」があるため、結果セットのすべての行はmy_table.fooの同じ値を持ち、したがって列の値はすべての行で同じで(inこの場合)これはクエリの行数です。

結果セットの行の合計数である各行に列を含める方法のより良い/簡単な例を次に示します。オプションのPartition By句を削除するだけです。

SELECT my_table.my_col, count(*) OVER() AS 'Count'
  FROM my_table
 WHERE my_table.foo = 'bar'
29
Adam Porad

クエリの実行と結果の取得から数ミリ秒で条件を満たす行の数が変わる可能性がある場合は、トランザクション内でクエリを実行できます/実行する必要があります。

BEGIN TRAN bogus

SELECT COUNT( my_table.my_col ) AS row_count
FROM my_table
WHERE my_table.foo = 'bar'

SELECT my_table.my_col
FROM my_table
WHERE my_table.foo = 'bar'
ROLLBACK TRAN bogus

これにより、常に正しい値が返されます。

さらに、SQL Serverを使用している場合は、@@ ROWCOUNTを使用して最後のステートメントの影響を受ける行数を取得し、realクエリの出力を一時テーブルまたはテーブル変数にリダイレクトできます。すべてをすべて返すことができ、トランザクションは不要です。

DECLARE @dummy INT

SELECT my_table.my_col
INTO #temp_table
FROM my_table
WHERE my_table.foo = 'bar'

SET @dummy=@@ROWCOUNT
SELECT @dummy, * FROM #temp_table
3
Joe Pineda

アプローチ2は、常に結果セットに一致するカウントを返します。

ただし、カウントの条件がデータセットの条件と一致することを保証するために、サブクエリを外部クエリにリンクすることをお勧めします。

SELECT 
  mt.my_row,
 (SELECT COUNT(mt2.my_row) FROM my_table mt2 WHERE mt2.foo = mt.foo) as cnt
FROM my_table mt
WHERE mt.foo = 'bar';
3
JosephStyons

ここにいくつかのアイデアがあります:

  • アプローチ1に進み、追加の結果を保持するために配列のサイズを変更するか、必要に応じて自動的にサイズ変更される型を使用します(使用している言語については言及しないので、詳細に説明することはできません)。
  • データベースがこれをサポートしている場合は、トランザクション内でアプローチ#1の両方のステートメントを実行して、カウントが両方とも同じであることを保証できます。
  • データで何をしているのかわかりませんが、最初にすべてを保存せずに結果を処理できる場合は、これが最良の方法かもしれません。
1
Robert Gamble

行カウントがselectカウントとselectステートメントの間で変化することを本当に心配している場合は、まず、一時テーブルに行を選択してみませんか?そうすれば、同期が取れることになります。

1
BoltBait
IF (@@ROWCOUNT > 0)
BEGIN
SELECT my_table.my_col
  FROM my_table
 WHERE my_table.foo = 'bar'
END
0
Deepfreezed

結果をベクトルに入れてみませんか?そうすれば、サイズを事前に知る必要はありません。

0
jonnii

これは、この質問に対するgoogleの上位結果であるため、追加するだけです。 sqliteでは、これを使用して行カウントを取得しました。

WITH temptable AS
  (SELECT one,two
   FROM
     (SELECT one, two
      FROM table3
      WHERE dimension=0
      UNION ALL SELECT one, two
      FROM table2
      WHERE dimension=0
      UNION ALL SELECT one, two
      FROM table1
      WHERE dimension=0)
   ORDER BY date DESC)
SELECT *
FROM temptable
LEFT JOIN
  (SELECT count(*)/7 AS cnt,
                        0 AS bonus
   FROM temptable) counter
WHERE 0 = counter.bonus
0
Tschallacka

このタイプのデータを処理するためのより良いパターンについて考えたいかもしれません。

回答が変更される可能性があるため(独自の問題を引き起こすトランザクションを使用しない限り)、行を返す前にクエリが返す行数を自己予測SQLドライバーが通知することはありません。

行数は変更されません-ACIDおよびSQLのgoogle。

0
dkretz