Doctrineのページネーション-SQL_CALC_FOUND_ROWS(MySQL)を使用してレコードの数を数えます

背景



少し前まで、制作のニーズのために、データベースを操作するための強力な人気のあるライブラリDoctrine 2を使用する素晴らしいSymfony 2フレームワークに出会いました。これには、ORM(オブジェクトリレーショナルマッパー)とDBAL(データベース抽象化)が含まれますレイヤー)。 ORMは、アプリケーションにオブジェクトの言語でデータベースと通信する機能を提供し、DBALは、PDO phpライブラリに基づいてクエリを記述することでデータにアクセスするための低レベルの方法です。 ORMは、複雑なビジネスアプリケーションを開発する際に多くの利点を提供しますが、同時に開発者がSQLクエリを直接記述する必要がないという事実に関連する多くの制限も課します-ORM Doctrineは、独自のオブジェクト指向クエリ言語を提供します。おなじみのSQLはすでに裏にあります。 これらの制限の1つに遭遇したので、それをどのように克服したかを共有したいと思います。 LIMIT制限を削除すると、リクエストによって返されるレコードの総数を取得することになります。



挑戦する



オブジェクトクエリ言語DQL(Doctrine Query Language)の境界に遭遇したときが来ました-MySQLが提供する1つの関数を使用したい、つまり、結果として行の数に制限がないかのように、クエリによって返されたレコードの数をカウントしたかった(LIMIT) 、この関数はSQL_CALC_FOUND_ROWSです。 いくつかの情報のページ分割を行う必要がある場合、同様の問題が常に発生します。 もちろん、このために、必要なレコードの合計数を返すLIMITなしで追加のSELECT COUNT(*)クエリを実行できますが、追加のクエリを必要としないため、私はより正しいように見えるため、最初の方法に興味がありましたデータベースは( 公式ドキュメントに基づいて)より速く動作します。 もちろん、この方法でアプリケーションがDBMSに依存するようになると長い間議論することができますが、手袋のようにDBMSを変更する多くのWebアプリケーションを見たことがありますか?

実際、Doctrine ORMのページネーションの問題を解決する方法はHabr-articlesの1つですでに説明さていますが、それは最初のDoctrineであり、私は2番目のDoctrineで作業し、解決策が判明しました。

したがって、前任者が説明した問題を繰り返します。クエリによって返されるレコードの総数を取得するには、SELECTステートメントの後にSQL_CALC_FOUND_ROWSキーワードを指定する必要があります。これは、LIMIT制限を考慮せずに、結果のサイズを計算するようMySQLに指示します。 クエリを実行した後、必要なサイズを取得するには、別のクエリSELECT FOUND_ROWS()を実行する必要があります。これは、大切な番号を返します。 ただし、MySQLは最初のクエリが実行されたときの行の総数に関する情報をすでに知っているため、2番目のクエリではデータの再フェッチは行われないことに注意してください。

したがって、上記の記事で説明したように、SQL_CALC_FOUND_ROWSという単語をDQLクエリのテキストに挿入しようとすると、



<?php

$query = $em->createQuery("SELECT SQL_CALC_FOUND_ROWS n FROM MyProject\Entity\News n WHERE n.date > '2011-01-01'");

$query->setMaxResults(10);

$news = $query->getResult();

...

?>







生成されたSQLタイプ:



SELECT SQL_CALC_FOUND_ROWS AS o__0 FROM News...







Doctrineはこの場所のテーブルフィールド(より正確にはエンティティオブジェクトのフィールド)の名前を見ることを期待しているため、もちろんこのクエリはエラーを引き起こします。



解決策



Doctrine 2には組み込みのクエリパーサーがあり、これはDQLクエリに基づいてツリー(Abstract Syntax Tree)を構築し、それがSQLクエリに変換されます。 SQL生成は、 カスタムASTウォーカーと呼ばれるメカニズムによって提供されます (「クローラー」と呼びます)。 クローラーには、出力ウォーカーとツリーウォーカーの2種類があります。 出力ウォーカー(1つのみ)は、クエリのSQLテキストの生成を直接担当し、ツリーウォーカー(任意の数)を使用して、SQLが生成される前にASTツリーをトラバースし、変更します。

これは出力ウォーカーであり、プラットフォーム固有の要求を生成するために使用できます。 この場合、私は次のことを行いました-WalkSelectClauseメソッドを次のようにオーバーライドすることにより、標準のSqlWalkerクラスを拡張しました。



<?php



namespace MyProject\Entity\Walkers;



use Doctrine\ORM\Query\SqlWalker;



class MysqlPaginationWalker extends SqlWalker {



/**

* Walks down a SelectClause AST node, thereby generating the appropriate SQL.

*

* @param $selectClause

* @return string The SQL.

*/

public function walkSelectClause($selectClause)

{

$sql = parent::walkSelectClause($selectClause);



if ($this->getQuery()->getHint('mysqlWalker.sqlCalcFoundRows') === true) {

if ($selectClause->isDistinct) {

$sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_CALC_FOUND_ROWS', $sql);

} else {

$sql = str_replace('SELECT', 'SELECT SQL_CALC_FOUND_ROWS', $sql);

}

}



return $sql;

}

}

...

?>







その後、アプリケーションコードは次のように変更されます。



<?php

$query = $em->createQuery("SELECT n FROM MyProject\Entity\News n WHERE n.date > '2011-01-01'");

$query->setMaxResults(10);



// Doctrine,

// , ,

$query->setHint(

\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER,

MyProject\\Entity\\Walkers\\MysqlPaginationWalker'

);

$query->setHint("mysqlWalker.sqlCalcFoundRows", true);



$news = $query->getResult();



// ,

$totalCount = $em->getConnection()->query('SELECT FOUND_ROWS()')->fetchColumn(0);

...

?>









以上です。 私の経験が誰かに役立つことを願っています。



All Articles