二重クエリなしのMySQLページネーション?


115

MySQLクエリから結果の数を取得し、同時に結果を制限する方法があるかどうか疑問に思っていました。

(私が理解しているように)ページネーションが機能する方法、最初に私は次のようなことをします

query = SELECT COUNT(*) FROM `table` WHERE `some_condition`

num_rows(query)を取得すると、結果の数がわかります。しかし、結果を実際に制限するには、次のような2番目のクエリを実行する必要があります。

query2 = SELECT COUNT(*) FROM `table` WHERE `some_condition` LIMIT 0, 10

私の質問:とにかく、与えられる結果の総数を取得し、単一のクエリで返される結果を制限する方法はありますか?または、これを行うためのより効率的な方法。ありがとう!


7
query2にCOUNT(*)はありませんが
dlofrodloh

回答:


66

いいえ、それはページ分割を行いたいアプリケーションの数です。クエリを2回作成しますが、信頼性が高く、防弾です。しかし、数秒間数をキャッシュすることができ、それは非常に役立ちます。

もう1つの方法は、SQL_CALC_FOUND_ROWS句を使用してを呼び出すことSELECT FOUND_ROWS()です。FOUND_ROWS()後で電話をかけなければならないという事実は別として、これには問題があります。MySQLにORDER BYクエリに影響を与え、大きなテーブルで2つのクエリの単純なアプローチよりも遅くなるというバグがあります


2
ただし、トランザクション内で2つのクエリを実行しない限り、競合状態を完全に証明することはできません。ただし、これは通常は問題になりません。
NickZoic 2009年

「信頼できる」とは、SQL自体が常に必要な結果を返すことを意味し、「防弾」とは、使用できるSQLを妨げるMySQLのバグがないことを意味します。SQL_CALC_FOUND_ROWSをORDER BYとLIMITで使用するのとは異なり、私が言及したバグによると。
staticsan 2009年

5
複雑なクエリでは、SQL_CALC_FOUND_ROWSを使用して同じクエリのカウントをフェッチする方が、ほとんどの場合、2つの別々のクエリを実行するよりも遅くなります。これは、制限に関係なくすべての行を完全に取得する必要があるため、LIMIT句で指定された行のみが返されるためです。リンクがある私の応答も参照してください。
thomasrutter、2011

これが必要な理由に応じて、合計結果を取得しないことを検討することもできます。自動ページングメソッドを実装することは、より一般的な方法になりつつあります。Facebook、Twitter、Bing、Googleなどのサイトは、古くからこの方法を使用しています。
Thomas B

68

私はほとんど2つのクエリを実行しません。

必要以上の行を返すだけで、ページには10行だけが表示されます。表示されている行が複数ある場合は、「次へ」ボタンを表示します。

SELECT x, y, z FROM `table` WHERE `some_condition` LIMIT 0, 11
// iterate through and display 10 rows.

// if there were 11 rows, display a "Next" button.

クエリは、最初に最も関連性の高い順に返されます。たぶん、ほとんどの人は412のうち236ページに行くことを気にしないでしょう。

グーグル検索を実行して、結果が最初のページにない場合、おそらく9ページではなく2ページに移動します。


42
実際、Googleクエリの最初のページで見つからない場合は、通常9ページにスキップします。
フィル

3
@フィル私は前にこれを聞いたが、なぜそれをするのですか?
TK123 2012年

5
少し遅れましたが、ここに私の推論があります。一部の検索は、検索エンジン最適化リンクファームによって支配されています。したがって、最初の数ページは、ポジション番号1を目指して戦っているさまざまなファームです。有用な結果は、トップではなく、クエリに関連付けられている可能性があります。
Phil

4
COUNT集約関数です。カウントすべての結果を1つのクエリでどのように返しますか?上記のクエリは、何LIMITが設定されていても、1行のみを返します。を追加するとGROUP BY、すべての結果が返されますが、結果COUNTは不正確になります
pixelfreak

2
:これはPerconaが推奨するアプローチの一つである percona.com/blog/2008/09/24/...
techdude

26

二重クエリを回避する別のアプローチは、最初にLIMIT句を使用して現在のページのすべての行をフェッチし、最大数の行が取得された場合にのみ2番目のCOUNT(*)クエリを実行することです。

多くのアプリケーションでは、最も可能性の高い結果は、すべての結果が1ページに収まることであり、ページ付けを行う必要があるのは、標準ではなく例外です。これらの場合、最初のクエリは最大数の結果を取得しません。

たとえば、stackoverflowの質問に対する回答が2ページ目に流れ込むことはほとんどありません。回答へのコメントが5件の制限を超えてこぼれることはほとんどなく、すべてを表示する必要があります。

したがって、これらのアプリケーションでは、最初にLIMITを使用してクエリを実行するだけで、その制限に達していない限り、2番目のCOUNT(*)クエリを実行する必要なく、行数が正確にわかります。ほとんどの状況をカバーします。


1
@thomasrutter私は同じアプローチをしましたが、今日それで欠陥を発見しました。結果の最終ページには、ページ分割データが含まれません。つまり、各ページに25の結果があり、最後のページにはそれほど多くない可能性があり、7があるとしましょう...つまり、count(*)は実行されないため、ページ付けは表示されません。ユーザー。
duellsy 2012

2
いいえ-200件の結果が返された場合、次の25件に対してクエリを実行しても7件しか返されません。これは、結果の総数が207であるため、COUNT(*)で別のクエリを実行する必要がないことを示します。あなたはそれが何を言おうとしているのかすでに知っているからです。ページネーションを表示するために必要なすべての情報があります。ユーザーにページネーションが表示されないという問題がある場合は、別の場所にバグがあります。
thomasrutter 2012

15

ほとんどの状況では、直感に反しているように見えますが、1つのクエリで実行するよりも、2つの別々のクエリで実行する方がはるかに高速でリソース消費量が少なくなります。

SQL_CALC_FOUND_ROWSを使用する場合、大きなテーブルの場合、最初のCOUNT(*)を使用したクエリとLIMITを使用した2つのクエリを実行するよりも、クエリの速度が大幅に低下します。これは、SQL_CALC_FOUND_ROWSにより、行をフェッチする前ではなく後に LIMIT句が適用されるため、制限を適用する前にすべての可能な結果について行全体をフェッチするためです。実際にはデータをフェッチするため、これはインデックスでは満足できません。

2つのクエリアプローチを使用する場合、最初のクエリはCOUNT(*)のみをフェッチし、実際のデータはフェッチしません。通常はインデックスを使用でき、実際の行データをフェッチする必要がないため、これはより迅速に満たされます。それが見るすべての行。次に、2番目のクエリは、最初の$ offset + $ limit行を確認して返すだけです。

MySQLパフォーマンスブログのこの投稿では、これについてさらに説明しています。

http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

ページネーションの最適化の詳細については、この投稿この投稿を確認してください。


2

私の答えは遅いかもしれませんが、2番目のクエリを(制限付きで)スキップして、バックエンドスクリプトで情報をフィルターするだけで済みます。たとえばPHPでは、次のようなことができます。

if($queryResult > 0) {
   $counter = 0;
   foreach($queryResult AS $result) {
       if($counter >= $startAt AND $counter < $numOfRows) {
            //do what you want here
       }
   $counter++;
   }
}

しかし、もちろん、考慮すべき数千のレコードがある場合、それは非常に非効率的になります。事前に計算された数を調べておくとよいでしょう。

これについては、次の記事をご覧ください。http//www.percona.com/ppc2009/PPC2009_mysql_pagination.pdf


リンクが死んでいる、これは正しいものだと思います:percona.com/files/presentations/ppc2009/…。編集されているかどうかわからないため、編集しないでください。
hectorg87 14

1
query = SELECT col, col2, (SELECT COUNT(*) FROM `table`) AS total FROM `table` WHERE `some_condition` LIMIT 0, 10

16
このクエリは、テーブル内のレコードの総数を返すだけです。条件に一致するレコードの数ではありません。
ローレンスバルサンティ

1
レコードの総数は、ページネーションに必要な数です(@Lawrence)。
imme 2014年

ああ、まあ、where内側のクエリに句を追加するだけで、ページングされた結果と共に正しい「合計」が得られます(limit句でページが選択されます
Erenor Paz


1

2020年に答えを探している人のために。MySQLのドキュメントに従って:

"SQL_CALC_FOUND_ROWSクエリ修飾子と付随するFOUND_ROWS()関数はMySQL 8.0.17で非推奨になり、将来のMySQLバージョンで削除されます。代わりに、LIMITを使用してクエリを実行し、次にCOUNT(*)を使用して2番目のクエリを実行することを検討してください追加の行があるかどうかを判断するためのLIMITなし。」

それで解決すると思います。

https://dev.mysql.com/doc/refman/8.0/en/information-functions.html#function_found-rows


0

ほとんどのクエリをサブクエリで再利用して、識別子に設定できます。たとえば、ランタイムで「s」という文字の順序を含む映画を検索する映画クエリは、私のサイトでは次のようになります。

SELECT Movie.*, (
    SELECT Count(1) FROM Movie
        INNER JOIN MovieGenre 
        ON MovieGenre.MovieId = Movie.Id AND MovieGenre.GenreId = 11
    WHERE Title LIKE '%s%'
) AS Count FROM Movie 
    INNER JOIN MovieGenre 
    ON MovieGenre.MovieId = Movie.Id AND MovieGenre.GenreId = 11
WHERE Title LIKE '%s%' LIMIT 8;

私はデータベースの専門家ではないことに注意してください。誰かがそれを少しだけ最適化できるようになることを望んでいます。SQLコマンドラインインターフェイスから直接実行しているので、私のラップトップではどちらも約0.02秒かかります。


-14
SELECT * 
FROM table 
WHERE some_condition 
ORDER BY RAND()
LIMIT 0, 10

3
これは質問の答えにはなりません。また、ランドによる注文は非常に悪い考えです。
Dan Walmsley 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.