ページネーションのために、LIMIT
句とOFFSET
句を使用してクエリを実行する必要があります。しかし、LIMIT
句とOFFSET
句を使用せずに、そのクエリによって返される行数のカウントも必要です。
実行したい:
SELECT * FROM table WHERE /* whatever */ ORDER BY col1 LIMIT ? OFFSET ?
そして:
SELECT COUNT(*) FROM table WHERE /* whatever */
同時に。それを行う方法、特にPostgresがそれを最適化する方法があり、両方を個別に実行するよりも速くなりますか?
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
を含めることで(冗長的に)支払うことができます。