Doctrineのページネーション(SQL_CALC_FOUND_ROWSを使用)

バージョン4.0以降、レコードの数がLIMITによって制限されている場合、MySQL DBMSにはクエリに一致するすべてのレコードの数を数える便利な機能があります。 データベース内の検索を使用するとき、および多数のレコードを持つテーブルからフェッチするとき、このような機能は単に必要です。 この記事では、Doctrine ORMでこの機能を使用する方法を説明します





このページネーションの方法が選択されたことをすぐに言いたいのは、 それが使用されている現在のプロジェクトとの互換性を実装する必要がありました。



構文



SELECTクエリでは、列リストの前にSQL_CALC_FOUND_ROWSオプションを指定する必要があります。 SELECT構文の構文の説明の始まりは次のとおりです。

選択

[すべて| DISTINCT | DISTINCTROW]

[HIGH_PRIORITY]

[STRAIGHT_JOIN]

[SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]

[SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]

select_expr、... ...



したがって、SELECT SQL_CALC_FOUND_ROWSクエリを実行すると、DBMSはクエリ条件に一致する行の合計数を計算し、この数をメモリに保存します。 当然、制約(LIMIT)を使用する場合にのみSELECT SQL_CALC_FOUND_ROWSを照会するのが理にかなっています。 サンプリング用のクエリを実行した直後に、レコード数を取得するには、別のSELECTクエリを実行する必要があります:SELECT FOUND_ROWS();。 その結果、MySQLは1つのフィールドで1つの行を返し、そこに行数が格納されます。



クエリ自体の例:

SELECT SQL_CALC_FOUND_ROWS * FROM tbl_name WHERE number> 100 LIMIT 10;

SELECT FOUND_ROWS();



最初のクエリは、条件番号> 100が満たされるtbl_nameテーブルの10行を返します(出力)。SELECTコマンドの2番目の呼び出しは、LIMIT式なしで記述された場合に最初のSELECTコマンドが返す行の数を返します。 SELECT SQL_CALC_FOUND_ROWSコマンドを使用する場合、MySQLは結果セットのすべての行を再計算する必要がありますが、クライアントに結果を送信する必要がないため、このメソッドはLIMITを使用しない場合よりも高速です。



kurtkrutの投稿Valery Leontyevによるこのデザインの使用に関する詳細を読むことができます。



挑戦する



したがって、Doctrineサンプルのページネーションを行うには、次のメソッドを使用できます。

  1. データベースからすべてのレコードを選択し、必要な条件を決定します。 私はあなたがそうしないことを望みます:)
  2. 2つのクエリを使用します。 最初のレコードの数を決定するためのカウント(id)のLIMIT部分のないものと、フェッチのために直接LIMITのあるもの。 多くの人がこのようにページネーションを行います。 彼は、わざわざではなくomezを使うようにアドバイスしました。 しかし、私のプロジェクトでは、すでにステップ4を使用しました
  3. 基本的にステップ2と同じことを行うDoctrine_Pagerを使用します
  4. SELECTクエリでSQL_CALC_FOUND_ROWS式を使用する


Doctrine_Pagerでは、すべてが基本的に明確です。 以下に例を示します。

//ページネーションのパラメータを定義します

$ currentPage = 1; //現在のページ

$ resultsPerPage = 50; //ページごとの結果の数



// DQLクエリに基づいてページネーターオブジェクトを作成します

$ pager =新しいDoctrine_Pager(

Doctrine_Query :: create()

->( 'User u')から、

$ currentPage、

$ resultsPerPage

);



次に、現在のページに必要なレコードをオブジェクトに正確に取得します。 詳細はこちらをご覧ください



問題



ただし、SQL_CALC_FOUND_ROWSの使用はそれほど簡単ではありません。 このページネーションの方法( ここここ )に対する批判にもかかわらず、あるいは、現在のプロジェクトとの互換性の理由から 、DoctrineでSQL_CALC_FOUND_ROWSを使用することが必要になりました。 しかし、その後、継続的なホイッスルが始まりました。 DQL(Doctrine Query Language)のselect()で使用すると、次のクエリが生成されました。



DQLから

$ q = Doctrine_Query :: create()

->選択( 'SQL_CALC_FOUND_ROWS *')

-> from( 'User u');


次のSQLが判明しました

SELECT SQL_CALC_FOUND_ROWS AS o__0 FROMユーザー...


この「AS o__0」はすべてを台無しにしました:) Doctrineはフィールド名としてSQL_CALC_FOUND_ROWSを取りました。



解決策



タンバリンと何時間も踊り、教義の腸を拾うことについては語りません。むしろ、この問題をどのように解決したかについて書きたいと思います。 まず、DQLおよびDBALを介してSQL_CALC_FOUND_ROWSを何らかの方法でSQLクエリにスローする必要がありました。 これは、独自のクエリクラスを使用して、関数parseSelect($ dql)および_buildSqlQueryBase()を変更することで解決しました。

<?php

クラスMyDoctrine_QueryはDoctrine_Queryを拡張します

{

パブリック関数parseSelect($ dql)

{



...

// DISTINCTキーワードを確認します

if($ first === 'DISTINCT'){

$ this-> _ sqlParts ['distinct'] = true;



$ refs [0] = substr($ refs [0]、++ $ pos);

}

/ * ここでは、SQL_CALC_FOUND_ROWSクエリに存在チェックを追加します

そして、もしそうなら、同じ名前のリクエストパラメータの値をtrueに設定します

* /



if($ first === 'SQL_CALC_FOUND_ROWS'){

$ this-> _ sqlParts ['sql_calc_found_rows'] = true;

$ refs [0] = substr($ refs [0]、++ $ pos);

}

...

}



保護された関数_buildSqlQueryBase()

{

スイッチ($ this-> _ type){

case self :: DELETE:

$ q = 'DELETE FROM';

休憩;

ケース自己::更新:

$ q = '更新';

休憩;

ケースの自己:: SELECT:

$ distinct =($ this-> _ sqlParts ['distinct'])? 'DISTINCT': '';

/ * そして、ここで実際にリクエストに式を追加します * /

$ sql_calc_found_rows =($ this-> _ sqlParts ['sql_calc_found_rows'])? 'SQL_CALC_FOUND_ROWS': '';

$ q = 'SELECT' $ sql_calc_found_rows。 ' '。 明確な$。 implode( '、'、$ this-> _ sqlParts ['select'])。 'FROM';

休憩;

}

リターン$ q;

}

}

?>



次に、Doctrineにクエリクラスを使用するように指示する必要があります。

$ manager = Doctrine_Manager :: getInstance();

require_once(ディレクトリ名(__ FILE__)。 '/lib/doctrine_extra/MyDoctrine/Query.php');

$ manager-> setAttribute(Doctrine :: ATTR_QUERY_CLASS、 'MyDoctrine_Query');



その後、SQL_CALC_FOUND_ROWSを使用してDQLを実行するには、select()部分で指定するだけで十分です。

$ q = Doctrine_Query :: create()

->選択( 'SQL_CALC_FOUND_ROWS *')

->( 'User u')から

->制限(10);


丁寧な読者が尋ねますが、LIMIT部分なしで取得するレコードの数を取得する方法。

これを行うには、 EventListenerを記述します。

<?php

クラスMyDoctrine_EventListener_SqlCalcFoundRowsはDoctrine_EventListenerを拡張します{



private static $ foundRows = null;



/ * リクエストが完了した直後に呼び出されます。これが必要です。 * /

パブリック関数postQuery(Doctrine_Event $イベント){

$ pdo = Doctrine_Manager :: connection()-> getDbh();

$ sql = "SELECT FOUND_ROWS()";

$ stmt = $ pdo-> query($ sql);

$ result = $ stmt-> fetch();

$ count = $ result ['FOUND_ROWS()'];

self :: $ foundRows =(int)$ count;

}



/ * 見つかったレコードの数を返します * /

public static function getFoundRowsCount(){

return self :: $ foundRows;

}

}

?>



システムに接続します:

require_once(dirname(__ FILE__)。 '/lib/doctrine_extra/MyDoctrine/EventListener/SqlCalcFoundRows.php');

$ conn-> addListener(新しいMyDoctrine_EventListener_SqlCalcFoundRows());



そして、次のコマンドでレコードの数を取得できるようになりました。

MyDoctrine_EventListener_SqlCalcFoundRows :: getFoundRowsCount();



この関数をDostrineコレクションに統合すると非常に便利です。

$ q = Doctrine_Query :: create()

->選択( 'SQL_CALC_FOUND_ROWS *')

->( 'User u')から

-> where( 'u.status = "active")

->制限(10)

->オフセット(5);

$ allCount = $ users-> getFoundRowsCount();



これを行うには、Doctrine_Collectionも継承する必要があります

<?php

クラスMyDoctrine_CollectionはDoctrine_Collectionを拡張します

{

/ **

* 式SQL_CALC_FOUND_ROWSを使用した以前のクエリの結果数を返します

* /

パブリック関数getFoundRowsCount(){

if(in_array( 'MyDoctrine_EventListener_SqlCalcFoundRows'、get_declared_classes())){

return MyDoctrine_EventListener_SqlCalcFoundRows :: getFoundRowsCount();

} else {

NULLを返します。

}

}

}

?>



ブートストラップに接続します

require_once(dirname(__ FILE__)。 '/lib/doctrine_extra/MyDoctrine/Collection.php');

$ manager-> setAttribute(Doctrine :: ATTR_COLLECTION_CLASS、 'MyDoctrine_Collection');



誰かがSQL_CALC_FOUND_ROWSを使用する必要がある場合、彼がこの記事を見つけて、私と同じ熊手を踏まないことを願っています:)

興味があれば、Doctrineをどのように完成させるかについてのトピックをもっと書きます。



PS omezは開発に貢献しました。



All Articles