18
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JoolenAdvent Calendar 2019

Day 5

Doctrineでちょっと複雑なSQLを発行する

Last updated at Posted at 2019-12-04

Doctrineでちょっと複雑なSQLを発行しようとすると躓くことが多いです(個人的に)。
ハマったポイントをいくつかまとめてみたいと思います。

SQLで使える関数が使えない

もちろん特定のRDBMSに依存していないライブラリなので、それぞれのRDBMSで使える関数は使えません。
でもDoctrineで使える関数もいくつかあります。
以下のページにまとまっています。

DQL Functions
https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#dql-functions

上記のリンクは公式のリファレンスですが、
コードサンプルがないのが悲しいところです。
例えばDATE_ADDを使いたいときは

DATE_ADD(date, days, unit) - Add the number of days to a given date. (Supported units are DAY, MONTH)

この一文を見て使い方を予測します。
今から1ヶ月後は

DATE_ADD(CURRENT_DATE(), 1, 'MONTH')

今から1日前は

DATE_ADD(CURRENT_DATE(), '-1', 'DAY')

※-1にはシングルクォートをつけないと怒られます。

で、Supported units are DAY, MONTHとあるようにYEARには対応していません。
もちろんエラーです。

よく使うものはだいたいあるかなという感じですが、個人的にはMySQLにあるDATE_FORMAT(OracleならTO_CHAR)がないのが辛いです。
DBに登録されている日付から日だけ取得するとか、
時間を切り捨てて日付のみの比較をするようなことができないからです。
次はDATE_FORMATについて書いてみます。

DATE_FORMATが使えない

個人的には結構辛いものがあるのですが、
なにかうまい代替方法はあるものでしょうか・・・?
大抵は渡す引数を工夫したりしますが、どうしてもDATE_FORMATを使いたいときはDQL関数拡張という機能を使います。

ドキュメントはこちら
DQL User Defined Functions
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/cookbook/dql-user-defined-functions.html

ドキュメントにはサンプルコードがあります。
サンプルコードを見れば関数の追加自体は簡単そうに見えますが、
関数の中身を書くのはちょっと手間そうです。

そんなとき、いろいろな関数をまとめて実装してくださっているコードがgithub上で提供されています(ありがたい)。
DoctrineExtensions
https://github.com/beberlei/DoctrineExtensions

こちらを導入することで、DATE_FORMATも使えるようになります。
手順はサンプルコードにあるので割愛。

FROM句でサブクエリを使いたい

Doctrineではサポートしていません。
過去に要望もあったようですが、却下されたようです。

DDC-2793: Subquery into FROM
https://github.com/doctrine/orm/issues/3542

FROM句でサブクエリを使うようなときはネイティブなクエリを使ってとのことです。

ネイティブなクエリを発行する

本当に複雑なSQLを発行するときは結局はここにたどり着きます。
以下のような感じで発行できます。


$sql = <<<___SQL
SELECT
    *
FROM
    order
WHERE
    order_id = :order_id
___SQL;

$params = ['order_id' => $order_id];
$em = EntityManager::create($dbParams, $config); // EntityManagerのgetはそれぞれのやり方で
$stmt = $em->getConnection()->prepare($sql);
$stmt->execute($params);
$result = $stmt->fetchAll();

ネイティブなクエリを発行したもののEntityと紐付けるのがめんどう

上記のサンプルだと、orderテーブルの中身を全部返しているのでOrderエンティティを返してほしいところです。
そんなときはResultSetMappingBuilderを使います。

$sql = <<<___SQL
SELECT
    *
FROM
    order o
WHERE
    order_id = :order_id
___SQL;

$rsm = new ResultSetMappingBuilder($em);
$rsm->addRootEntityFromClassMetadata('Path\to\Order\Entity', 'o');
$query = $em->createNativeQuery($sql, $rsm);
$query->setParameter('order_id', $order_id);
$orders = $query->getResult();

おわりに

まだまだDoctrineを使いこなしていませんが、いろいろ調べていきたいと思います!

18
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?