DB からのデータをページングする場合、ページ ジャンプ コントロールをレンダリングするために必要なページ数を知る必要があります。
現在、クエリを 2 回実行することでこれを行っています。1 回目は でラップしcount()
て合計結果を決定し、2 回目は現在のページに必要な結果だけを取得するために制限を適用しています。
これは非効率に思えます。LIMIT
が適用される前に返された結果の数を判断するより良い方法はありますか?
PHPとPostgresを使用しています。
DB からのデータをページングする場合、ページ ジャンプ コントロールをレンダリングするために必要なページ数を知る必要があります。
現在、クエリを 2 回実行することでこれを行っています。1 回目は でラップしcount()
て合計結果を決定し、2 回目は現在のページに必要な結果だけを取得するために制限を適用しています。
これは非効率に思えます。LIMIT
が適用される前に返された結果の数を判断するより良い方法はありますか?
PHPとPostgresを使用しています。
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
がベース クエリの行数と同じかそれ以上の場合、行は返されません。したがって、 no も取得しますfull_count
。可能な代替:
SELECT
クエリ内のイベントのシーケンス( 0. CTE は個別に評価され、実体化されます。Postgres 12 以降では、プランナーは動作する前にサブクエリのようなものをインライン化する場合があります。) ここではありません。
WHERE
句(およびJOIN
条件、例にはありませんが)は、ベーステーブルからの適格な行をフィルタリングします。残りは、フィルタリングされたサブセットに基づいています。( 2.GROUP BY
および集計関数はここに配置されます。) ここにはありません。
( 3.SELECT
グループ化/集約された列に基づいて、他のリスト式が評価されます。) ここではありません。
OVER
ウィンドウ関数は、関数の句とフレームの指定に応じて適用されます。シンプルcount(*) OVER()
は、すべての条件を満たす行に基づいています。
ORDER BY
( 6. DISTINCT
or DISTINCT ON
would go here.) ここじゃない。
LIMIT
/OFFSET
返される行を選択するために確立された順序に基づいて適用されます。LIMIT
/OFFSET
テーブル内の行数が増えると、ますます非効率的になります。より良いパフォーマンスが必要な場合は、別のアプローチを検討してください。
影響を受ける行の数を取得するには、まったく異なるアプローチがあります ( &が適用される前の完全な数ではありません)。Postgres には、最後の SQL コマンドによって影響を受けた行数の内部簿記があります。一部のクライアントは、その情報にアクセスしたり、行自体をカウントしたりできます (psql など)。OFFSET
LIMIT
たとえば、次のようにSQL コマンドを実行した直後に、影響を受ける行の数をplpgsqlで取得できます。
GET DIAGNOSTICS integer_var = ROW_COUNT;
pg_num_rows
または、PHPで使用できます。または他のクライアントの同様の機能。
関連している:
私のブログで説明しているように、MySQL にはSQL_CALC_FOUND_ROWSという機能があります。これにより、クエリを 2 回実行する必要がなくなりますが、たとえ limit 句でクエリを早期に停止できたとしても、クエリ全体を実行する必要があります。
私の知る限り、PostgreSQL には同様の機能はありません。ページネーションを行うときに注意すべきことの1つ(LIMITが使用される最も一般的なこと):「OFFSET 1000 LIMIT 10」を実行すると、たとえ10行しか得られなくても、DBは少なくとも1010行をフェッチする必要があります.より効率的な方法は、前の行 (この場合は 1000 番目) に対して注文する行の値を記憶し、次のようにクエリを書き直すことです: "... WHERE order_row > value_of_1000_th LIMIT 10". 利点は、「order_row」がおそらく索引付けされていることです (そうでない場合は、問題が発生します)。欠点は、ページ ビュー間で新しい要素が追加された場合、同期が少しずれてしまう可能性があることです (ただし、訪問者には見えない可能性があり、パフォーマンスが大幅に向上する可能性があります)。
毎回 COUNT() クエリを実行しないことで、パフォーマンスの低下を軽減できます。クエリが再度実行される 5 分前など、ページ数をキャッシュします。膨大な数の INSERT が表示されない限り、問題なく動作するはずです。
Postgresはすでにある程度のキャッシュ処理を行っているため、このタイプの方法は見た目ほど非効率的ではありません。実行時間が2倍になることは間違いありません。DBレイヤーにタイマーが組み込まれているので、証拠を見てきました。
ページングの目的で知っておく必要があることを見て、完全なクエリを 1 回実行し、データをサーバー側のキャッシュとしてディスクに書き込み、それをページング メカニズムにフィードすることをお勧めします。
ユーザーにデータを提供するかどうかを決定する目的で COUNT クエリを実行している場合 (つまり、X 件以上のレコードがある場合はエラーを返します)、COUNT アプローチに固執する必要があります。