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


101

ページネーションの目的で、LIMITandOFFSET句を使用してクエリを実行する必要があります。ただし、LIMITandOFFSET句なしでそのクエリによって返される行数のカウントも必要です。

実行したい:

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

そして:

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

同時に。それを行う方法、特にPostgresに最適化させて、両方を個別に実行するよりも高速にする方法はありますか?


回答:


178

はい。単純なウィンドウ関数を使用すると:

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

コストは総数がない場合よりも大幅に高くなりますが、通常は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;

が大きすぎるfull_count場合OFFSETは、NULL値が1行追加されます。それ以外の場合は、最初のクエリと同様にすべての行に追加されます。

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

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

最終的なソート順をサポートするインデックスが利用可能な場合ORDER BYは、CTEに(冗長に)含めることで料金が発生する可能性があります。


3
LIMITと条件の両方で、返される行がありますが、指定されたオフセットでは結果を返しません。そのような状況では、どのようにして行数を取得できますか?
ダニマシュー2018年

非常に素晴らしい、ありがとう、ページネーション、データテーブルを使用するときにうまく機能します。これをSQLの先頭に追加して使用し、合計数の追加クエリを保存します。
Ahmed Sunny

入力パラメータを介してクエリでカウントを動的に有効にできる場合、これについて詳しく説明していただけますか?同様の要件がありますが、インラインカウントが必要かどうかはユーザーが決定します。
julealgon

1
@julealgon:定義の詳細から新しい質問を始めてください。コンテキストのためにいつでもこれにリンクし、必要に応じてここにコメントを残してリンクを戻す(そして私の注意を引く)ことができます。
Erwin Brandstetter 2018年

1
@JustinL。:追加されたオーバーヘッドは、比較的安価な基本クエリの場合にのみ重要になります。また、Postgres12は複数の方法でCTEパフォーマンスを改善しました。(このCTEはMATERIALIZEDデフォルトのままですが、2回参照されます。)
ErwinBrandstetter20年

0

編集:この回答は、フィルタリングされていないテーブルを取得するときに有効です。それが誰かを助けることができるが、それが最初の質問に正確に答えないかもしれない場合に備えて、私はそれをさせます。

正確な値が必要な場合は、ErwinBrandstetterの答えが最適です。ただし、大きなテーブルでは、かなり適切な近似値のみが必要になることがよくあります。Postgresはまさにそれ提供し、各行を評価する必要がないため、はるかに高速になります。

SELECT *
FROM (
    SELECT *
    FROM tbl
    WHERE /* something */
    ORDER BY /* something */
    OFFSET ?
    LIMIT ?
    ) data
RIGHT JOIN (SELECT reltuples FROM pg_class WHERE relname = 'tbl') pg_count(total_count) ON true;

を外部化する利点があるのRIGHT JOINか、それとも標準のクエリのようにする利点があるのか​​、実際にはよくわかりません。それはいくつかのテストに値するでしょう。

SELECT t.*, pgc.reltuples AS total_count
FROM tbl as t
RIGHT JOIN pg_class pgc ON pgc.relname = 'tbl'
WHERE /* something */
ORDER BY /* something */
OFFSET ?
LIMIT ?

2
高速カウントの見積もりについて:stackoverflow.com/a/7945274/939860あなたが言ったように:テーブル全体を取得するときに有効です-これはWHEREクエリの句と矛盾しています。2番目のクエリは論理的に間違っており(DB内のテーブルごとに1行を取得します)、修正するとコストが高くなります。
ErwinBrandstetter20年

0

一方でアーウィンBrandstetterの答えは魔法のように動作し、それが行の合計数を返すすべての行で、次のように:

col1 - col2 - col3 - total
--------------------------
aaaa - aaaa - aaaa - count
bbbb - bbbb - bbbb - count
cccc - cccc - cccc - count

次のように、合計数を1回だけ返すアプローチの使用を検討することをお勧めします。

total - rows
------------
count - [{col1: 'aaaa'},{col2: 'aaaa'},{col3: 'aaaa'}
         {col1: 'bbbb'},{col2: 'bbbb'},{col3: 'bbbb'}
         {col1: 'cccc'},{col2: 'cccc'},{col3: 'cccc'}]

SQLクエリ:

SELECT 
    (SELECT COUNT(*) FROM table) as count, 
    (SELECT json_agg(t.*) FROM (
        SELECT * FROM table
        WHERE /* whatever */
        ORDER BY col1
        OFFSET ?
        LIMIT ?
    ) AS t) AS rows 

-6

戻り結果の行の総数を取得するために、Justに対して同じクエリを2回呼び出すのは悪い習慣です。実行に時間がかかり、サーバーリソースを浪費します。

より良いことにSQL_CALC_FOUND_ROWS、制限クエリ結果とともに行数の総数をフェッチするようにMySQLに指示するクエリで使用できます。

次のように設定された例:

SELECT SQL_CALC_FOUND_ROWS employeeName, phoneNumber FROM employee WHERE employeeName LIKE 'a%' LIMIT 10;

SELECT FOUND_ROWS();

上記のクエリでSQL_CALC_FOUND_ROWSは、残りの必須クエリにオプションを追加して2行目を実行するSELECT FOUND_ROWS()だけです。つまり、そのステートメントによって返される結果セットの行数を返します。


1
このソリューションには、mysqlではなくpostgresが必要です。
MuffinMan

@ MuffinMan、mysqlでも同じように使用できます。MYSQL 4.0以降、クエリでSQL_CALC_FOUND_ROWSオプションが使用されています。しかし、MYSQL8.0からはそれが廃止されました。
MohdRashid19年

関係ありません。この質問は何年も前に答えられました。貢献したい場合は、同じ件名でMySQLに固有の新しい質問を投稿してください。
MuffinMan

常に関連性がある
AliHussain20年

-15

番号。

おそらく、内部で十分に複雑な機械を使用して個別に実行するよりも、理論的にはわずかな利益が得られる可能性があります。ただし、条件に一致する行の数を知りたい場合は、LIMITEDサブセットだけでなく、それらをカウントする必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.