web-dev-qa-db-ja.com

LIMIT / OFFSETでクエリを実行し、行の総数も取得します

ページネーションのために、LIMIT句とOFFSET句を使用してクエリを実行する必要があります。しかし、LIMIT句とOFFSET句を使用せずに、そのクエリによって返される行数のカウントも必要です。

実行したい:

SELECT * FROM table WHERE /* whatever */ ORDER BY col1 LIMIT ? OFFSET ?

そして:

SELECT COUNT(*) FROM table WHERE /* whatever */

同時に。それを行う方法、特にPostgresがそれを最適化する方法があり、両方を個別に実行するよりも速くなりますか?

51
Tim

Yes.単純なウィンドウ関数を使用する場合:

SELECT *, count(*) OVER() AS full_count
FROM   tbl
WHERE  /* whatever */
ORDER  BY col1
LIMIT  ?
OFFSET ?

コストは合計数がない場合よりも大幅に高くなりますが、2つの個別のクエリよりも安くなることに注意してください。どちらの場合でも、Postgresは実際にすべての行をカウントする必要があります。これにより、条件を満たす行の総数に応じてコストがかかります。詳細:

ただしDaniが指摘したようにOFFSETが少なくともベースクエリから返される行数と同じ場合、行は返されません。そのため、full_countも取得しません。

それが受け入れられない場合、常にフルカウントを返す可能性のある回避策は、CTEとOUTER JOINを使用することです。

WITH cte AS (
   SELECT *
   FROM   tbl
   WHERE  /* whatever */
   )
SELECT *
FROM  (
   TABLE  cte
   ORDER  BY col1
   LIMIT  ?
   OFFSET ?
   ) sub
RIGHT  JOIN (SELECT count(*) FROM cte) c(full_count) ON true;

OFFSETが大きすぎる場合は、full_countが付加されたNULL値の行を取得します。または、最初のクエリのようにすべての行に追加されます。

すべてのNULL値を持つ行が有効な結果である可能性がある場合、offset >= full_countをチェックして、空の行の起源を明確にする必要があります。

これは、基本クエリを1回だけ実行します。ただし、クエリにオーバーヘッドが追加され、カウントに対して基本クエリを繰り返すよりも少ない場合にのみ支払われます。

最終的な並べ替え順序をサポートするインデックスが利用可能な場合、CTEにORDER BYを含めることで(冗長的に)支払うことができます。

100