web-dev-qa-db-ja.com

LIMITが適用される前に結果数を取得する最良の方法

DBからのデータをページングする場合、ページジャンプコントロールをレンダリングするために必要なページ数を知る必要があります。

現在私はそれを2回クエリを実行することで行っています。1回はcount()にラップして合計結果を決定し、もう1回は制限を適用して現在のページに必要な結果のみを取得します。

これは非効率的です。 LIMITが適用される前に返される結果の数を判断するより良い方法はありますか?

PHPおよびPostgresを使用しています。

56

純粋なSQL

状況は2008年から変更されています。 ウィンドウ関数 を使用して、1つのクエリで完全なカウントと制限された結果を取得できます。 ( 2009のPostgreSQL 8.4 で導入されました)。

_SELECT foo
     , count(*) OVER() AS full_count
FROM   bar
WHERE  <some condition>
ORDER  BY <some col>
LIMIT  <pagesize>
OFFSET <offset>
_

これは合計数がない場合よりもかなり高価になる可能性があることに注意してください。すべての行を数える必要があり、一致するインデックスから先頭行だけを取得するショートカットが役に立たなくなる可能性があります。
小さなテーブルや_full_count_ <= OFFSET + LIMITではそれほど重要ではありません。かなり大きな_full_count_の問題。

コーナーケースOFFSETが少なくともベースクエリの行数と同じである場合、 行なしが返されます。したがって、_full_count_も取得しません。可能な代替:

イベントのシーケンスを考えます

  1. WHERE句(およびJOIN条件、ただしここではありません)は、ベーステーブルから条件を満たす行をフィルタリングします。

    (_GROUP BY_および集約関数はここにあります。)

  2. ウィンドウ関数は、(OVER句と関数のフレーム仕様に応じて)すべての条件を満たす行を考慮して適用されます。単純なcount(*) OVER()はすべての行に基づいています。

  3. _ORDER BY_

    DISTINCTまたは_DISTINCT ON_がここに入ります。)

  4. LIMIT/OFFSETは、確立された順序に基づいて適用され、返される行を選択します。

LIMIT/OFFSETは、テーブルの行数が増えると非効率になります。より良いパフォーマンスが必要な場合は、代替アプローチを検討してください。

最終的なカウントを取得するための代替手段

影響を受ける行の数を取得するための完全に異なるアプローチがあります(notOFFSETLIMITが適用されました)。 Postgresには、最後のSQLコマンドによって影響を受けた行数が内部的に記録されています。一部のクライアントは、その情報にアクセスしたり、行自体をカウントしたりできます(psqlなど)。

たとえば、次のコマンドを使用してSQLコマンドを実行した直後に、plpgsqlの影響を受ける行の数を取得できます。

_GET DIAGNOSTICS integer_var = ROW_COUNT;
_

マニュアルの詳細。

または、 _pg_num_rows_ in[〜#〜] php [〜#〜] を使用できます。または他のクライアントの同様の機能。

関連:

118

私のブログ について説明しているように、MySQLには SQL_CALC_FOUND_ROWS という機能があります。これにより、クエリを2回実行する必要がなくなりますが、制限句によって早期に停止できたとしても、クエリ全体を実行する必要があります。

私の知る限り、PostgreSQLには同様の機能はありません。ページネーションを行う際に注意するべき1つのこと(LIMITがIMHOで使用される最も一般的なこと):「OFFSET 1000 LIMIT 10」を実行することは、DBが少なくともフェッチする必要があることを意味します1010行のみ。たとえ10行しか与えられない場合でも、より効率的な方法は、前の行(この場合は1000番目)に対して注文した行の値を覚えて、次のようなクエリ: "... WHERE order_row> value_of_1000_th LIMIT 10"。利点は、 "order_row"がおそらくインデックス化されていることです(そうでない場合、問題が発生します)。欠点は、ページビューの間に新しい要素が追加された場合、多少の同期が取れなくなる可能性があることです(ただし、訪問者はこれを観察できず、パフォーマンスが大幅に向上する可能性があります)。

5
Grey Panther

毎回COUNT()クエリを実行しないことで、パフォーマンスの低下を軽減できます。クエリが再度実行される前に、たとえば5分間ページ数をキャッシュします。大量のINSERTが表示されない限り、問題なく動作するはずです。

1
Bob Somers

Postgresはすでに一定量のキャッシングを行っているので、このタイプのメソッドは見かけほど非効率ではありません。実行時間が2倍になることは間違いありません。タイマーがDBレイヤーに組み込まれているので、証拠を見てきました。

0
grantwparks