LIMITが適用される前に結果カウントを取得する最良の方法
-
03-07-2019 - |
質問
DBからのデータをページングする場合、ページジャンプコントロールをレンダリングするために必要なページ数を知る必要があります。
現在、クエリを2回実行します。1回はcount()
でラップして合計結果を決定し、2回目は現在のページに必要な結果だけを取得するために制限を適用します。
これは非効率的です。 LIMIT
が適用される前に返される結果の数を判断するより良い方法はありますか?
PHPとPostgresを使用しています。
解決
純粋な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
<!> lt; = OFFSET
+ LIMIT
の場合は重要ではありません。大幅に大きなWHERE
の問題。
コーナーケース :JOIN
がベースクエリの行数以上の場合、 行なし > が返されます。したがって、GROUP BY
も取得しません。可能な代替案:
一連のイベントを考慮してください:
-
OVER
句(およびcount(*) OVER()
条件、ただしここではありません)は、ベーステーブルから修飾行をフィルタリングします。(
ORDER BY
および集約関数はここに行きます。) -
ウィンドウ関数は、すべての修飾行を考慮して適用されます(
DISTINCT
句と関数のフレーム仕様に依存します)。単純なDISTINCT ON
はすべての行に基づいています。 -
pg_num_rows
(<=>または<=>はここに移動します。)
-
<=> / <=>は確立された順序に基づいて適用され、返す行を選択します。
<=> / <=>は、テーブル内の行数が増加するにつれて、ますます非効率的になります。より良いパフォーマンスが必要な場合は、別のアプローチを検討してください。
最終カウントを取得するための代替案
影響を受ける行のカウントを取得するには、まったく異なる方法があります(<=> <!> amp; <=>が適用される前のフルカウントではない )。 Postgresには、最後のSQLコマンドの影響を受ける行数を内部で記録する機能があります。一部のクライアントは、その情報にアクセスしたり、自分で行をカウントしたりできます(psqlなど)。
たとえば、次のようにSQLコマンドを実行した直後に、 plpgsql で影響を受ける行の数を取得できます。
GET DIAGNOSTICS integer_var = ROW_COUNT;
または PHPで <=>を使用できます 。または、他のクライアントの同様の機能。
関連:
他のヒント
私のブログで説明しているように、MySQL SQL_CALC_FOUND_ROWS という機能があります。これにより、クエリを2回実行する必要がなくなりますが、たとえ制限句によってクエリが早期に停止することを許可したとしても、クエリ全体を実行する必要があります。
私が知る限り、PostgreSQLに類似した機能はありません。ページネーションを行うときの注意点(LIMITが使用されている最も一般的なもの):<!> quot; OFFSET 1000 LIMIT 10 <!> quot;は、10行のみを取得する場合でも、DBが少なくとも1010行をフェッチする必要があることを意味します。よりパフォーマンスの高い方法は、前の行に対して並べ替える行の値を記憶することです(この場合は1000番目)、次のようにクエリを書き換えます:<!> quot; ... WHERE order_row <!> gt; value_of_1000_th LIMIT 10 <!> quot;。利点は、<!> quot; order_row <!> quot;ほとんどの場合、インデックスが作成されます(そうでない場合は、問題が発生しています)。欠点は、ページビューの間に新しい要素が追加されると、少し同期が取れなくなる可能性があることです(ただし、それでも訪問者が確認できない場合があり、パフォーマンスが大幅に向上する可能性があります)。
COUNT()クエリを毎回実行しないことで、パフォーマンスの低下を軽減できます。クエリが再実行される5分前のページ数をキャッシュします。膨大な数のINSERTが表示されない限り、それはうまく機能するはずです。
Postgresはすでに一定量のキャッシュ処理を行っているため、このタイプの方法は見かけほど効率的ではありません。実行時間は2倍になることはありません。 DBレイヤーにタイマーが組み込まれているので、証拠を見てきました。
ページングの目的で知る必要がある場合は、完全なクエリを1回実行し、データをサーバー側キャッシュとしてディスクに書き込み、ページングメカニズムを介してフィードすることをお勧めします。
ユーザーにデータを提供するかどうかを決定する目的でCOUNTクエリを実行している場合(つまり、<!> gt; Xレコードがある場合、エラーを返します)、固執する必要がありますCOUNTアプローチ。