Doctrineでサブクエリを使う

こんにちは、小川です。
今週はsymfony 1.2がリリースされ、Jobeetという新たなチュートリアルが始まったりとsymfony界隈がとても盛り上がっていますね。symfonyに力を入れてるアシアルとしてもうれしい限りです。

さて、symfony 1.2にデフォルトで含まれているDoctrine。そのDoctrineでデータベースからオブジェクトを取得するにはDoctrine_Queryというオブジェクトを使いますが、実はこのDoctrine_Queryはサブクエリにも対応しています。今回はそのサブクエリの使い方をご紹介したいと思います。

今回使うスキーマは以下になります。


User:
  actAs:
    Timestampable:
  columns:
    id:
      type: integer(4)
      primary: true
      notnull: true
      autoincrement: true
    email:
      type: string(255)
      notnull: true
    password:
      type: string(50)
      notnull: true
Order:
  actAs:
    Timestampable:
  columns:
    id:
      type: integer(4)
      primary: true
      notnull: true
      autoincrement: true
    user_id:
      type: integer(4)
      notnull: true
    total:
      type: integer(4)
      notnull: true
  relations:
    User:
      foreignAlias: Orders

では、早速サブクエリを使ってオブジェクトを取得してみましょう。
以下は購入平均額が3000円を超えている優良ユーザをサブクエリを用いて取得する例です。


<?php
class UserTable extends Doctrine_Table
{
  public function getPrimeUsers($threshold = 3000)
  {
    $q = $this->createQuery('u');
    // サブクエリを作成
    $q2 = $q->createSubquery()->select('o.user_id')
      ->from('Order o')
      ->groupBy('o.user_id')
      ->having('AVG(o.total) >= ?', $threshold);
    //echo $q2->getDql();
    //=> SELECT o.user_id FROM Order o
    //     GROUP BY o.user_id HAVING AVG(o.total) >= ?
    // サブクエリをメインのクエリに入れる
    $q->where("u.id IN ({$q2->getDql()})");
    return $q->execute();
  }
}
$primeUsers = Doctrine::getTable('User')->getPrimeUsers();

まずcreateQuery()で基準となるDoctrine_Queryオブジェクトを生成します。
そのオブジェクトにcreateSubquery()というメッセージを送ると、もう1つDoctrine_Queryオブジェクトが生成されます。このオブジェクトは内部でサブクエリフラグを持っており、このオブジェクトに対してサブクエリの条件をつけていきます。

そしてこのサブクエリを適応する方法は至って単純です。getDql()メソッドで文字列にして、where文の中にいれるだけです。ただし、ここでプレースホルダを使用するとシングルコーテーションで囲まれてクエリとして認識されないので、直接文字列の内部に入れてください。

ちなみにサブクエリを用いるときに、SELECT句やFROM句の中にもサブクエリを使いたい場合があると思います。少し試してみただけなのですが、SELECT句にはサブクエリを使えるものの、FROM句の中では使えないようです。というのも、どうやらDQLでは、FROM句には必ず実在するクラス名を指定する必要があるようです。FROM句にサブクエリを使いたい場合は素直にSQLを書きましょう。

symfony 1.2にはDoctrineが最初から含まれていますので、今までPropelを使っていた方々もぜひ使ってみてください。今後もsymfonyやDoctrineに関する様々な情報やノウハウを配信していく予定ですので、どうぞよろしくお願いいたします。