This project is no longer maintained and has been archived. |
はじめに
Doctrine Query Language (DQL)は複雑なオブジェクト読み取りを手助けするためのObject Query Languageです。リレーショナルデータを効率的に読み取るときに(例えばユーザーと電話番号を取得するとき)DQL(もしくは生のSQL)を使うことを常に考えるべきです。
この章ではDoctrine Query Languageの使い方の例をたくさん実演します。これらすべての例では[doc defining-models :name]の章で定義したスキーマを使うことを想定します。またテスト用に1つの追加モデルを定義します。
// models/Account.php
class Account extends Doctrine_Record { public function setTableDefinition() { $this->hasColumn('name', 'string', 255); $this->hasColumn('amount', 'decimal'); } }
YAMLフォーマットでの同じ例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:
# schema.yml
Account: columns: name: string(255) amount: decimal
生のSQLを使う場合と比較すると、DQLは次の恩恵があります:
- 始めから結果セットの列ではなくレコード(オブジェクト)の読み取りのために設計されている
- DQLはリレーションを理解するのでSQLのjoinとjoinの条件を手動で入力する必要がない
- DQLは異なるデータベースでポータブルである
- DQLはレコード制限などとても複雑なアルゴリズムが組み込まれておりこれは開発者がオブジェクトを効率的に読み取るのを手助けする
- 条件付きの取得で一対多、多対多のリレーショナルデータを扱うときに時間を節約できる機能をサポートする
DQLの力が十分でなければ、オブジェクト投入に対して[doc native-sql RawSql API]を使うことを考えるべきです。
既に次の構文に慣れている方もいらっしゃるでしょうx:
CAUTION 次のコードは決して使わないでください。 これはオブジェクト投入用に多くのSQLクエリを使います。
// test.php
// ... $users = Doctrine_Core::getTable('User')->findAll();
foreach($users as $user) { echo $user->username . " has phonenumbers: ";
foreach($user->Phonenumbers as $phonenumber) {
echo $phonenumber->phonenumber . "\n";
}
}
// test.php |
// ... $q = Doctrine_Query::create() ->from('User u') ->leftJoin('u.Phonenumbers p');
echo $q->getSqlQuery();
上記のクエリによって生成されるSQLを見てみましょう:
SELECT u.id AS uid, u.is\_active AS uis_active, u.is_super_admin
AS uis\_super\_admin, u.first\_name AS ufirst_name, u.last_name AS ulast\_name, u.username AS uusername, u.password AS upassword, u.type AS utype, u.created_at AS ucreated\_at, u.updated\_at AS uupdated_at, p.id AS pid, p.user\_id AS puser_id, p.phonenumber AS p__phonenumber FROM user u LEFT JOIN phonenumber p ON u.id = p.user_id
クエリを実行してデータで遊んでみましょう:
// test.php
// ... $users = $q->execute();
foreach($users as $user) { echo $user->username . " has phonenumbers: ";
foreach($user->Phonenumbers as $phonenumber) {
echo $phonenumber->phonenumber . "\n";
}
}
CAUTION DQLの文字列で二重引用符(")を使うのは非推奨です。これはMySQLの標準では使えますがDQLにおいて識別子と混同される可能性があります。代わりに値に対してプリペアードステートメントを使うことが推奨されます。これによって適切にエスケープされます。
SELECTクエリ
SELECT
文の構文:
SELECT [ALL | DISTINCT] , ... [FROM [WHERE ] [GROUP BY [ASC | DESC],
... ] [HAVING ] [ORDER BY [ASC | DESC], ...] [LIMIT OFFSET }]
SELECT
文は1つもしくは複数のコンポーネントからデータを読み取るために使われます。
それぞれのselect_expr
は読み取りたいカラムもしくは集約関数の値を示します。
すべてのSELECT
文で少なくとも1つのselect_expr
がなければなりません。
最初にサンプルのAccount
レコードをinsertします:
// test.php
// ... $account = new Account(); $account->name = 'test 1'; $account->amount = '100.00'; $account->save();
$account = new Account(); $account->name = 'test 2'; $account->amount = '200.00'; $account->save();
test.php
を実行します:
$ php test.php
次のサンプルクエリでデータのselectをテストできます:
// test.php
// ... $q = Doctrine_Query::create() ->select('a.name') ->from('Account a');
echo $q->getSqlQuery();
上記のクエリによって生成されたSQLを見てみましょう:
SELECT a.id AS aid, a.name AS aname FROM account a
// test.php
// ... $accounts = q->execute(); print_r(
accounts->toArray());
上記の例では次の出力が生み出されます:
$ php test.php Array ( [0] => Array ( [id] => 1 [name] => test 1
[amount] => )
[1] => Array
(
[id] => 2
[name] => test 2
[amount] =>
)
)
アスタリスクは任意のコンポーネントからすべてのカラムをselectするために使われます。アスタリスクを使うときでも実行されるSQLクエリは実際にはそれを使いません(Doctrineはアスタリスクを適切なカラムの名前に変換することで、データベースでのパフォーマンスの向上につながります)。
// test.php
// ... $q = Doctrine_Query::create() ->select('a.*') ->from('Account a');
echo $q->getSqlQuery();
最後のクエリの例から生成されたSQLとすぐ前に生成されたクエリで生成されたSQLを比較します:
SELECT a.id AS aid, a.name AS aname, a.amount AS a__amount FROM
account a
NOTE アスタリスクは
Account
モデルに存在する実際のすべてのカラム名に置き換えられることに留意してください。
クエリを実行して結果を検査してみましょう:
// test.php
// ... $accounts = q->execute(); print_r(
accounts->toArray());
上記の例は次の出力を生み出します:
$ php test.php Array ( [0] => Array ( [id] => 1 [name] => test 1
[amount] => 100.00 )
[1] => Array
(
[id] => 2
[name] => test 2
[amount] => 200.00
)
)
FROM
句はレコードから読み取るコンポーネントを示します。
// test.php
// ... $q = Doctrine_Query::create() ->select('u.username, p.*') ->from('User u') ->leftJoin('u.Phonenumbers p')
echo $q->getSqlQuery();
getSql()
への上記の呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, u.username AS uusername, p.id AS p**id,
p.user_id AS p**user_id, p.phonenumber AS p__phonenumber FROM user u LEFT JOIN phonenumber p ON u.id = p.user_id
WHERE
句は、選択されるためにレコードが満たさなければならない条件を示します。where_condition
は選択されるそれぞれの列に対してtrueに表示する式です。WHERE
句が存在しない場合ステートメントはすべての列を選択します。
// test.php
// ... $q = Doctrine_Query::create() ->select('a.name') ->from('Account a') ->where('a.amount > 2000');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT a.id AS aid, a.name AS aname FROM account a WHERE a.amount >
2000
WHERE
句において、集約(要約)関数を除いて、DQLがサポートする任意の関数と演算子を使うことができます。HAVING
句は集約関数で結果を絞るために使うことができます:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.username') ->from('User u') ->leftJoin('u.Phonenumbers p') ->having('COUNT(p.id) > 3');
echo $q->getSqlQuery();
getSql()
を呼び出すと次のSQLクエリが出力されます:
SELECT u.id AS uid, u.username AS uusername FROM user u LEFT JOIN
phonenumber p ON u.id = p.user_id HAVING COUNT(p.id) > 3
ORDER BY
句は結果のソートに使われます。
// test.php
// ... $q = Doctrine_Query::create() ->select('u.username') ->from('User u') ->orderBy('u.username');
echo $q->getSqlQuery();
上記のgetSql()
を呼び出すと次のSQLクエリが出力されます:
SELECT u.id AS uid, u.username AS uusername FROM user u ORDER BY
u.username
LIMIT
とOFFSET
句はレコードの数をrow_count
に効率的に制限するために使われます。
// test.php
// ... $q = Doctrine_Query::create() ->select('u.username') ->from('User u') ->limit(20);
echo $q->getSqlQuery();
上記のgetSql()
を呼び出すと次のSQLクエリが出力されます:
SELECT u.id AS uid, u.username AS uusername FROM user u LIMIT 20
集約値
集約値用のSELECT
構文:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id, COUNT(t.id) AS num_threads') ->from('User u, u.Threads t') ->where('u.id = ?', 1) ->groupBy('u.id');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, COUNT(f.id) AS f0 FROM user u LEFT JOIN
forum__thread f ON u.id = f.user_id WHERE u.id = ? GROUP BY u.id
クエリを実行して結果をインスペクトします:
// test.php
// ... $users = $q->execute();
次のコードでnum_threads
のデータに簡単にアクセスできます:
// test.php
// ... echo $users->num_threads . ' threads found';
UPDATEクエリ
UPDATE
文の構文:
UPDATE SET = , = WHERE ORDER BY LIMIT
UPDATE
文はcomponent_name
の既存のレコードのカラムを新しい値で更新し影響を受けたレコードの数を返します。SET
句は修正するカラムとそれらに渡される値を示します。- オプションの
WHERE
句は更新するレコードを特定する条件を指定します。WHERE
句がなければ、すべてのレコードが更新されます。 - オプションの
ORDER BY
句はレコードが更新される順序を指定します。 LIMIT
句は更新できるレコードの数に制限をおきます。UPDATE
の範囲を制限するために`LIMIT row\_count``を使うことができます。``LIMIT``句は列を変更する制限ではなく**列にマッチする制限**です。実際に変更されたのかに関わらず``WHERE``句を満たす``record_count
`の列が見つかるとステートメントはすぐに停止します。
// test.php
// ... $q = Doctrine_Query::create() ->update('Account') ->set('amount', 'amount + 200') ->where('id > 200');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
UPDATE account SET amount = amount + 200 WHERE id > 200
更新の実行はシンプルです。次のクエリを実行するだけです:
// test.php
// ... $rows = $q->execute();
echo $rows;
DELETEクエリ
DELETE FROM WHERE ORDER BY LIMIT
DELETE
文はcomponent_name
からレコードを削除し削除されるレコードの数を返します。- オプションの
WHERE
句は削除するレコードを特定する条件を指定します。WHERE
句なしでは、すべてのレコードが削除されます。 - `
ORDER BY
`句が指定されると、指定された順序でレコードが削除されます。 LIMIT
句は削除される列の数に制限を置きます。record_count
の数のレコードが削除されると同時にステートメントは停止します。
// test.php
// ... $q = Doctrine_Query::create() ->delete('Account a') ->where('a.id > 3');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
DELETE FROM account WHERE id > 3
DELETE
クエリの実行は次の通りです:
// test.php
// ... $rows = $q->execute();
echo $rows;
NOTE DQLのUPDATEとDELETEクエリを実行すると影響を受けた列の数が返されます。
FROM句
構文:
FROM [[LEFT | INNER] JOIN ] ...
FROM
句はレコードを読み取るコンポーネントを示します。複数のコンポーネントを名付けると、joinを実行することになります。指定されたそれぞれのテーブルに対して、オプションとしてエイリアスを指定できます。
次のDQLクエリを考えます:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id') ->from('User u');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u__id FROM user u
User
はクラス(コンポーネント)の名前でu
はエイリアスです。常に短いエイリアスを使うべきです。大抵の場合これらによってクエリははるかに短くなるのと例えばキャッシュを利用するときに短いエイリアスが使われていればクエリのキャッシュされたフォームの取るスペースが少なくなるからです。
JOINの構文
DQL JOINの構文:
[[LEFT | INNER] JOIN ] [ON | WITH] [INDEXBY] , [[LEFT | INNER] JOIN
] [ON | WITH] [INDEXBY] , ... [[LEFT | INNER] JOIN ] [ON | WITH] [INDEXBY]
DQLはINNER JOINとLEFT JOINをサポートします。それぞれのjoinされたコンポーネントに対して、オプションとしてエイリアスを指定できます。
デフォルトのjoinの形式はLEFT JOIN
です。このjoinは`LEFT
JOIN``句もしくはシンプルな'``,
`'のどちらかを使うことで示せます。なので次のクエリは等しいです:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id, p.id') ->from('User u') ->leftJoin('u.Phonenumbers p');
$q = Doctrine_Query::create() ->select('u.id, p.id') ->from('User u, u.Phonenumbers p');
echo $q->getSqlQuery();
推奨される形式は前者です。より冗長で読みやすく何が行われているのか理解しやすいからです。 |
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, p.id AS pid FROM user u LEFT JOIN phonenumber p
ON u.id = p.user_id
NOTE JOINの条件が自動的に追加されることに注意してください。Doctrineは
User
とPhonenumber
は関連していることを知っているのであなたに代わって追加できるからです。
`INNER
JOIN``は共通集合を生み出します(すなわち、最初のコンポーネントのありとあらゆるレコードが2番目のコンポーネントのありとあらゆるレコードにjoinされます)。ですので例えば電話番号を1つかそれ以上持つすべてのユーザーを効率的に取得したい場合、基本的に``INNER
JOIN
`が使われます。
デフォルトではDQLは主キーのjoin条件を自動追加します:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id, p.id') ->from('User u') ->leftJoin('u.Phonenumbers p');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, p.id AS pid FROM User u LEFT JOIN Phonenumbers
p ON u.id = p.user_id
ONキーワード
このビヘイビアをオーバーライドして独自のカスタムjoin条件を追加したい場合ON
キーワードで実現できます。次のDQLクエリを考えてみましょう:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id, p.id') ->from('User u') ->leftJoin('u.Phonenumbers p ON u.id = 2');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, p.id AS pid FROM User u LEFT JOIN Phonenumbers
p ON u.id = 2
NOTE 通常追加される
ON
条件が現れず代わりにユーザーが指定した条件が使われていることに注目してください。
WITHキーワード
大体の場合最初のjoin条件をオーバーライドする必要はありません。むしろカスタム条件を追加したいことがあります。これはWITH
キーワードで実現できます。
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id, p.id') ->from('User u') ->leftJoin('u.Phonenumbers p WITH u.id = 2');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, p.id AS pid FROM User u LEFT JOIN Phonenumbers
p ON u.id = p.user_id AND u.id = 2
NOTE
ON
条件が完全には置き換えられていないことに注意してください。代わりに指定する条件が自動条件に追加されます。
Doctrine_Query
APIはJOINを追加するための2つのコンビニエンスメソッドを提供します。これらはinnerJoin()
とleftJoin()
と呼ばれ、これらの使い方は次のようにとても直感的です:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id') ->from('User u') ->leftJoin('u.Groups g') ->innerJoin('u.Phonenumbers p WITH u.id > 3') ->leftJoin('u.Email e');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u__id FROM user u LEFT JOIN user_group u2 ON u.id =
u2.user_id LEFT JOIN groups g ON g.id = u2.group_id INNER JOIN phonenumber p ON u.id = p.user_id AND u.id > 3 LEFT JOIN email e ON u.id = e.user_id
INDEXBYキーワード
INDEXBY
キーワードはコレクション/配列のキーなどの特定のカラムをマッピングする方法を提供します。デフォルトではDoctrineは数値のインデックス付きの配列/コレクションに複数の要素のインデックスを作成します。マッピングはゼロから始まります。このビヘイビアをオーバーライドするには下記で示されるようにINDEXBY
キーワードを使う必要があります:
// test.php
// ... $q = Doctrine_Query::create() ->from('User u INDEXBY u.username');
$users = $q->execute();
NOTE
INDEXBY
キーワードは生成されるSQLを変えません。コレクションのそれぞれのレコードのキーとして指定されたカラムでデータをハイドレイトするためにDoctrine_Query
によって内部で使われます。
これで$users
コレクションのユーザーは自身の名前を通してアクセスできます:
// test.php
// ... echo $user['jack daniels']->id;
INDEXBY
キーワードは任意のJOINに適用できます。これは任意のコンポーネントがそれぞれ独自のインデックス作成のビヘイビアを持つことができることを意味します。次のコードにおいてUsers
とGroups
の両方に対して異なるインデックス作成機能を使用しています。
// test.php
// ... $q = Doctrine_Query::create() ->from('User u INDEXBY u.username') ->innerJoin('u.Groups g INDEXBY g.name');
$users = $q->execute();
drinkers clubの作成日を出力してみましょう。
// test.php
// ... echo $users['jack daniels']->Groups['drinkers club']->createdAt;
WHERE句
構文:
WHERE
WHERE
句は、与えられた場合、選択されるためにレコードが満たさなければならない条件を示します。where_condition
は選択されるそれぞれの列に対してtrueに評価される式です。WHERE
句が存在しない場合ステートメントはすべての列を選択します。- 集約関数の値で結果を絞るとき
WHERE
句の代わりにHAVING
句が使われます。
Doctrine_Query
オブジェクトを使用する複雑なwhere条件を構築するためにaddWhere()
、andWhere()
、orWhere()
、whereIn()
、andWhereIn()
、orWhereIn()
、whereNotIn()
,
andWhereNotIn()
、orWhereNotIn()
メソッドを使うことができます。
すべてのアクティブな登録ユーザーもしくは管理者を読み取る例は次の通りです:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id') ->from('User u') ->where('u.type = ?', 'registered') ->andWhere('u.is_active = ?', 1) ->orWhere('u.is_super_admin = ?', 1);
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u__id FROM user u WHERE u.type = ? AND u.is_active =
? OR u.is_super_admin = ?
条件式
リテラル
文字列
文字列リテラルはシングルクォートで囲まれます; 例: 'literal'。シングルクォートを含む文字列リテラルは2つのシングルクォートで表現されます; 例: 'literal''s'。
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id, u.username') ->from('User u') ->where('u.username = ?', 'Vincent');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, u.username AS uusername FROM user u WHERE
u.username = ?
NOTE
where()
メソッドにusername
の値をパラメータとして渡したので生成SQLに含まれません。クエリを実行するときにPDOが置き換え処理をします。Doctrine_Query
インスタンス上でパラメータをチェックするにはgetParams()
メソッドを使うことができます。
整数
整数リテラルはPHPの整数リテラル構文の使用をサポートします。
// test.php
// ... $q = Doctrine_Query::create() ->select('a.id') ->from('User u') ->where('u.id = 4');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u__id FROM user u WHERE u.id = 4
浮動小数
浮動小数はPHPの浮動小数リテラルの構文の使用をサポートします。
// test.php
// ... $q = Doctrine_Query::create() ->select('a.id') ->from('Account a') ->where('a.amount = 432.123');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT a.id AS a__id FROM account a WHERE a.amount = 432.123
論理値
論理値リテラルはtrueとfalseです。
// test.php
// ... $q = Doctrine_Query::create() ->select('a.id') ->from('User u') ->where('u.is_super_admin = true');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u__id FROM user u WHERE u.is_super_admin = 1
列挙値
列挙値は文字リテラルと同じ方法で動作します。
// test.php
// ... $q = Doctrine_Query::create() ->select('a.id') ->from('User u') ->where("u.type = 'admin'");
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u__id FROM user u WHERE u.type = 'admin'
予め定義され予約済みのリテラルは大文字と小文字を区別しますが、これらを大文字で書くのが良い標準です。
入力パラメータ
位置パラメータの使用の例は次の通りです:
単独の位置パラメータ:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id') ->from('User u') ->where('u.username = ?', array('Arnold'));
echo $q->getSqlQuery();
NOTE 位置パラメータ用に渡されたパラメータが1つの値しか格納しないとき1つの値を含む配列の代わりに単独のスカラー値を渡すことができます。
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u__id FROM user u WHERE u.username = ?
複数の位置パラメータ:
// test.php
// ... $q = Doctrine_Query::create() ->from('User u') ->where('u.id > ? AND u.username LIKE ?', array(50, 'A%'));
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u__id FROM user u WHERE (u.id > ? AND u.username LIKE
?)
名前付きパラメータの使い方の例は次の通りです:
単独の名前付きパラメータ:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id') ->from('User u') ->where('u.username = :name', array(':name' => 'Arnold'));
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u__id FROM user u WHERE u.username = :name
LIKEステートメントを伴う名前付きパラメータ:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id') ->from('User u') ->where('u.id > :id', array(':id' => 50)) ->andWhere('u.username LIKE :name', array(':name' => 'A%'));
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u__id FROM user u WHERE u.id > :id AND u.username LIKE
:name
演算子と優先順位
演算子の一覧は優先順位が低い順です。
|| 演算子 || 説明 || || . || ナビゲーション演算子|| || || //算術演算子: // || || +, - || 単項式 || || *, / || 乗法と除法 || || +, - || 加法と減法 || || =, >, >=, <, <=, <> (not equal), || 比較演算子 || || [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY || || || || //論理演算子: // || || NOT || || || AND || || || OR || ||
IN式
構文:
IN (|)
//サブクエリ//の結果から//オペランド//が見つかるもしくは指定しされたカンマで区切られた//値リスト//にある場合IN
の条件式はtrueを返します。サブクエリの結果が空の場合IN
の式は常にfalseです。
//値リスト//が使われているときそのリストには少なくとも1つの要素がなければなりません。
IN
に対してサブクエリを使う例は次の通りです:
// test.php
// ... $q = Doctrine_Query::create() ->from('User u') ->where('u.id IN (SELECT u.id FROM User u INNER JOIN u.Groups g WHERE g.id = ?)', 1);
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid FROM user u WHERE u.id IN (SELECT u2.id AS u2id
FROM user u2 INNER JOIN user_group u3 ON u2.id = u3.user_id INNER JOIN groups g ON g.id = u3.group_id WHERE g.id = ?)
整数のリストを使うだけの例は次の通りです:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id') ->from('User u') ->whereIn('u.id', array(1, 3, 4, 5));
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u__id FROM user u WHERE u.id IN (?, ?, ?, ?)
LIKE式
構文:
string_expression [NOT] LIKE pattern_value [ESCAPE escape_character]
string_expressionは文字列の値でなければなりません。pattern_valueは文字列リテラルもしくは文字列の値を持つ入力パラメータです。アンダースコア(\_
)は任意の単独の文字を表し、パーセント(%
)の文字は文字のシーケンス(空のシーケンスを含む)を表し、そして他のすべての文字はそれら自身を表します。オプションのescape_characterは単独文字の文字列リテラルもしくは文字の値を持つ入力パラメータ(すなわちcharもしくはCharacter)でpattern_value
で特別な意味を持つアンダースコアとパーセントの文字をエスケープします。
例:
- address.phone LIKE '12%3'は'123'、'12993'に対してtrueで'1234'に対してfalseです。
- asentence.word LIKE 'l_se'は'lose'に対してtrueで'loose'に対してfalseです。
- aword.underscored LIKE '_%' ESCAPE ''は'_foo'に対してtrueで'bar'に対してfalseです。
- address.phone NOT LIKE '12%3'は'123'と'12993'に対してfalseで'1234'に対してtrueです。
string_expressionもしくはpattern_valueの値はNULLもしくはunknownで、LIKE式の値はunknownです。escape_characterが指定されNULLである場合、LIKE式の値はunknownです。
'@gmail.com'で終わるEメールを持つユーザーを見つける:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id') ->from('User u') ->leftJoin('u.Email e') ->where('e.address LIKE ?', '%@gmail.com');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u__id FROM user u LEFT JOIN email e ON u.id =
e.user_id WHERE e.address LIKE ?
'A'で始まる名前を持つすべてのユーザーを見つける:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id') ->from('User u') ->where('u.username LIKE ?', 'A%');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u__id FROM user u WHERE u.username LIKE ?
EXISTS式
構文:
[NOT ]EXISTS ()
EXISTS
演算子はサブクエリが1つもしくは複数の列を返す場合はTRUE
を返しそうでなければFALSE
を返します。
`NOT
EXISTS``演算子はサブクエリが0を返す場合``TRUE``を返しそうでなければ``FALSE
`を返します。
NOTE 次の例では
ReaderLog
モデルを追加する必要があります。
// models/ReaderLog.php
class ReaderLog extends Doctrine_Record { public function setTableDefinition() { $this->hasColumn('article_id', 'integer', null, array( 'primary' => true ) );
$this->hasColumn('user_id', 'integer', null, array(
'primary' => true
)
);
}
} YAMLフォーマットでの同じは次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:
# schema.yml
ReaderLog: columns: article_id: type: integer primary: true user_id: type: integer primary: true
NOTE
ReaderLog
モデルを追加した後でgenerate.php
スクリプトを実行することをお忘れなく!
$ php generate.php
これでテストを実行できます!最初に、読者を持つすべての記事を見つけます:
// test.php
// ... $q = Doctrine_Query::create() ->select('a.id') ->from('Article a') ->where('EXISTS (SELECT r.id FROM ReaderLog r WHERE r.article_id = a.id)');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT a.id AS aid FROM article a WHERE EXISTS (SELECT r.id AS rid
FROM reader_log r WHERE r.article_id = a.id)
読者を持たないすべての記事を見つけます:
// test.php
// ... $q = Doctrine_Query::create() ->select('a.id') ->from('Article a') ->where('NOT EXISTS (SELECT r.id FROM ReaderLog r WHERE r.article_id = a.id));
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT a.id AS a**id FROM article a WHERE NOT EXISTS (SELECT r.id AS
r**id FROM reader_log r WHERE r.article_id = a.id)
AllとAnyの式
構文:
operand comparison_operator ANY (subquery) operand
comparison_operator SOME (subquery) operand comparison_operator ALL (subquery)
サブクエリの結果のすべての値に対して比較演算子がtrueである場合もしくはサブクエリの結果が空の場合、ALLの条件式はtrueを返します。すべての条件式のALLは少なくとも1つの列に対して比較の結果がfalseである場合はfalseで、trueもしくはfalseのどちらでもない場合はunknownです。
$q = Doctrine_Query::create() ->from('C') ->where('C.col1 < ALL (FROM
C2(col1))');
サブクエリの結果の値に対して比較演算子がtrueの場合条件式のANYはtrueを返します。サブクエリの結果が空の場合もしくはサブクエリの結果のすべての値に対して比較式がfalseの場合、ANY条件式はfalseで、trueでもfalseでもなければunknownです。
$q = Doctrine_Query::create() ->from('C') ->where('C.col1 > ANY (FROM
C2(col1))');
SOMEキーワードはANY用のエイリアスです。
$q = Doctrine_Query::create() ->from('C') ->where('C.col1 > SOME (FROM
C2(col1))');
ALLもしくはANY条件式で使うことができる比較演算子は=、<、<=、>、>=、<>です。サブクエリの結果は条件式で同じ型を持たなければなりません。
NOT INは<> ALL用のエイリアスです。これら2つのステートメントは等しいです:
FROM C WHERE C.col1 <> ALL (FROM C2(col1)); FROM C WHERE C.col1 NOT IN
(FROM C2(col1));
$q = Doctrine_Query::create() ->from('C') ->where('C.col1 <> ALL (FROM
C2(col1))');
$q = Doctrine_Query::create() ->from('C') ->where('C.col1 NOT IN (FROM C2(col1))');
サブクエリ
サブクエリは通常のSELECTクエリが含むことができる任意のキーワードもしくは句を含むことができます。
サブクエリの利点です:
- これらはクエリを構造化するのでそれぞれの部分のステートメントを分離することが可能です。
- これらは複雑なjoinとunionを必要とするオペレーションを実行する代替方法を提供します。
- 多くの人の意見によればこれらは読みやすいです。本当に、人々に初期のSQL "Structured Query Language."と呼ばれるオリジナルのアイディアを与えたサブクエリのイノベーションでした。
idが1であるグループに所属しないすべてのユーザーを見つける例は次の通りです:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id') ->from('User u') ->where('u.id NOT IN (SELECT u2.id FROM User u2 INNER JOIN u2.Groups g WHERE g.id = ?)', 1);
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u**id FROM user u WHERE u.id NOT IN (SELECT u2.id AS
u2**id FROM user u2 INNER JOIN user_group u3 ON u2.id = u3.user_id INNER JOIN groups g ON g.id = u3.group_id WHERE g.id = ?)
グループに所属していないすべてのユーザーを見つける例は次の通りです。
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id') ->from('User u') ->where('u.id NOT IN (SELECT u2.id FROM User u2 INNER JOIN u2.Groups g)');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u**id FROM user u WHERE u.id NOT IN (SELECT u2.id AS
u2**id FROM user u2 INNER JOIN user_group u3 ON u2.id = u3.user_id INNER JOIN groups g ON g.id = u3.group_id)
関数式
文字列関数
//CONCAT//関数は引数を連結した文字列を返します。上記の例においてユーザーのfirst\_name
とlast_name
を連結してname
という値にマッピングします。
// test.php
// ... $q = Doctrine_Query::create() ->select('CONCAT(u.first_name, u.last_name) AS name') ->from('User u');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, CONCAT(u.first\_name, u.last\_name) AS u0 FROM
user u
これでクエリを実行してマッピングされた関数値を取得できます:
$users = $q->execute();
foreach($users as user) { // 'name'は
userのプロパティではなく、// マッピングされた関数値である echo $user->name; }
//SUBSTRING//関数の2番目と3番目の引数は開始位置と返される部分文字列の長さを表します。これらの引数は整数です。文字列の最初の位置は1によって表されます。//SUBSTRING//関数は文字列を返します。
// test.php
// ... $q = Doctrine_Query::create() ->select('u.username') ->from('User u') ->where("SUBSTRING(u.username, 0, 1) = 'z'");
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, u.username AS uusername FROM user u WHERE
SUBSTRING(u.username FROM 0 FOR 1) = 'z'
NOTE SQLは使用しているDBMSに対して適切な
SUBSTRING
構文で生成されることに注目してください!
//TRIM//関数は文字列から指定された文字をトリムします。トリムされる文字が指定されていない場合、スペース(もしくは空白)が想定されます。オプションのtrim_characterは単独文字の文字列リテラルもしくは文字の値を持つ入力パラメータです(すなわちcharもしくはCharacter)[30]。トリムの仕様が提供されていない場合、BOTHが想定されます。//TRIM//関数はトリムされた文字列を返します。
// test.php
// ... $q = Doctrine_Query::create() ->select('u.username') ->from('User u') ->where('TRIM(u.username) = ?', 'Someone');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, u.username AS uusername FROM user u WHERE
TRIM(u.username) = ?
//LOWER//と//UPPER//関数はそれぞれ文字列を小文字と大文字に変換します。これらは文字列を返します。
// test.php
// ... $q = Doctrine_Query::create(); ->select('u.username') ->from('User u') ->where("LOWER(u.username) = 'jon wage'");
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, u.username AS uusername FROM user u WHERE
LOWER(u.username) = 'someone'
//LOCATE//関数は文字列の範囲内で任意の文字列の位置を返します。検索は指定された位置で始められます。文字列が整数として見つかった位置で、これは最初の位置を返します。最初の引数は検索される文字列です; 2番目の引数は検索文字列です; 3番目のオプション引数は検索が始まる文字列の位置を表す整数です(デフォルトでは、検索文字列の始め)。文字列の最初の位置は1によって表現されます。文字列が見つからない場合、0が返されます。
//LENGTH//関数は文字の文字列の長さを整数として返します。
算術関数
利用可能なDQLの算術関数です:
ABS(simple_arithmetic_expression)
SQRT(simple_arithmetic_expression) MOD(simple_arithmetic_expression, simple_arithmetic_expression)
- //ABS//関数は与えられた数の絶対値を返します。
- //SQRT//関数は与えられた数の平方根を返します。
- //MOD//関数は2番目の引数で最初の引数を割ったときの余りを返します。
サブクエリ
はじめに
DoctrineではFROM、SELECTとWHERE文でDQLのサブクエリを使うことができます。下記のコードではDoctrineが提供する異なる型のすべてのサブクエリの例が見つかります。
サブクエリを利用する比較
指定されたグループに所属しないすべてのユーザーを見つける。
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id') ->from('User u') ->where('u.id NOT IN (SELECT u.id FROM User u INNER JOIN u.Groups g WHERE g.id = ?)', 1);
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u**id FROM user u WHERE u.id NOT IN (SELECT u2.id AS
u2**id FROM user u2 INNER JOIN user_group u3 ON u2.id = u3.user_id INNER JOIN groups g ON g.id = u3.group_id WHERE g.id = ?)
サブクエリでユーザーの電話番号を読み取りユーザー情報の結果セットに格納します。
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id') ->addSelect('(SELECT p.phonenumber FROM Phonenumber p WHERE p.user_id = u.id LIMIT 1) as phonenumber') ->from('User u');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, (SELECT p.phonenumber AS pphonenumber FROM
phonenumber p WHERE p.user_id = u.id LIMIT 1) AS u__0 FROM user u
GROUP BY、HAVING句
DQLのGROUP BY構文:
GROUP BY groupby_item {, groupby_item}*
DQL HAVINGの構文:
HAVING conditional_expression
`GROUP
BY``と``HAVING
`句は集約関数を扱うために使われます。次の集約関数がDQLで利用可能です:
COUNT
、MAX
、MIN
、AVG
、SUM
アルファベット順で最初のユーザーを名前で選択する。
// test.php
// ... $q = Doctrine_Query::create() ->select('MIN(a.amount)') ->from('Account a');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT MIN(a.amount) AS a__0 FROM account a
すべてのアカウントの合計数を選択する。
// test.php
// ... $q = Doctrine_Query::create() ->select('SUM(a.amount)') ->from('Account a');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT SUM(a.amount) AS a__0 FROM account a
GROUP BY句を含まないステートメントで集約関数を使うと、すべての列でグルーピングすることになります。下記の例ではすべてのユーザーと彼らが持つ電話番号の合計数を取得します。
// test.php
// ... $q = Doctrine_Query::create() ->select('u.username') ->addSelect('COUNT(p.id) as num_phonenumbers') ->from('User u') ->leftJoin('u.Phonenumbers p') ->groupBy('u.id');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, u.username AS uusername, COUNT(p.id) AS p__0
FROM user u LEFT JOIN phonenumber p ON u.id = p.user_id GROUP BY u.id
HAVING
句は集約値を使用する結果を狭めるために使われます。次の例では少なくとも2つの電話番号を持つすべてのユーザーを取得します。
// test.php
// ... $q = Doctrine_Query::create() ->select('u.username') ->addSelect('COUNT(p.id) as num_phonenumbers') ->from('User u') ->leftJoin('u.Phonenumbers p') ->groupBy('u.id') ->having('num_phonenumbers >= 2');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, u.username AS uusername, COUNT(p.id) AS p**0
FROM user u LEFT JOIN phonenumber p ON u.id = p.user_id GROUP BY u.id HAVING p**0 >= 2
次のコードで電話番号の数にアクセスできます:
// test.php
// ... $users = $q->execute();
foreach($users as $user) { echo $user->name . ' has ' . $user->num_phonenumbers . ' phonenumbers'; }
ORDER BY句
はじめに
レコードのコレクションはORDER BY句を使用してデータベースレベルで効率的にソートできます。
構文:
[ORDER BY {ComponentAlias.columnName} [ASC | DESC], ...]
例:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.username') ->from('User u') ->leftJoin('u.Phonenumbers p') ->orderBy('u.username, p.phonenumber');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, u.username AS uusername FROM user u LEFT JOIN
phonenumber p ON u.id = p.user_id ORDER BY u.username, p.phonenumber
逆順でソートするためにソートするORDER BY句のカラム名にDESC(降順)キーワードを追加できます。デフォルトは昇順です; これはASCキーワードを使用して明示的に指定できます。
// test.php
// ... $q = Doctrine_Query::create() ->select('u.username') ->from('User u') ->leftJoin('u.Email e') ->orderBy('e.address DESC, u.id ASC');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, u.username AS uusername FROM user u LEFT JOIN
email e ON u.id = e.user_id ORDER BY e.address DESC, u.id ASC
集約値でソートする
次の例ではすべてのユーザーを取得しユーザーが持つ電話番号の数でユーザーをソートします。
// test.php
// ... $q = Doctrine_Query::create() ->select('u.username, COUNT(p.id) count') ->from('User u') ->innerJoin('u.Phonenumbers p') ->orderby('count');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, u.username AS uusername, COUNT(p.id) AS p**0
FROM user u INNER JOIN phonenumber p ON u.id = p.user_id ORDER BY p**0
ランダム順を使う
次の例ではランダムな投稿を取得するために`ORDER
BY
`句でランダム機能を使います。
// test.php
// ... $q = Doctrine_Query::create() ->select('t.id, RANDOM() AS rand') ->from('Forum_Thread t') ->orderby('rand') ->limit(1);
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT f.id AS fid, RAND() AS f0 FROM forumthread f ORDER BY f0
LIMIT 1
LIMITとOFFSET句
おそらく最も複雑機能であるDQLパーサーはLIMIT
句パーサーです。DQL
LIMIT句パーサーはLIMIT
データベースポータビリティを考慮するだけでなく複雑なクエリ分析とサブクエリを使用することで列の代わりにレコードの数を制限できる機能を持ちます。
最初の20ユーザーと関連する電話番号を読み取ります:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.username, p.phonenumber') ->from('User u') ->leftJoin('u.Phonenumbers p') ->limit(20);
echo $q->getSqlQuery();
|
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, u.username AS uusername, p.id AS p**id,
p.phonenumber AS p**phonenumber FROM user u LEFT JOIN phonenumber p ON u.id = p.user_id
ドライバーのポータビリティ
DQLのLIMIT
句はサポートされるすべてのデータベース上でポータブルです。次の事実に対して特別な注意を払う必要があります:
- Mysql、PgsqlとSqliteのみがLIMIT / OFFSET句をネイティブに実装します。
- Oracle / Mssql / FirebirdではLIMIT / OFFSET句はドライバー専用の方法でシミュレートされる必要があります
- limit-subquery-algorithmはmysqlで個別にサブクエリを実行する必要があります。まだmysqlがサブクエリでLIMIT句をサポートしていないからです。
- PgsqlはSELECT句で保存するフィールドごとの順序を必要とします。limit-subquery-algorithmがpgsqlドライバーが使われるときに考慮される必要があるからです。
- Oracleは< 30個未満のオブジェクト識別子のみを許可します(= テーブル/カラム 名前/エイリアス)、limitサブクエリは可能な限りショートエイリアスを使いメインクエリでエイリアスの衝突を回避しなければなりません。
limit-subquery-algorithm
limit-subquery-algorithmはDQLパーサーが内部で使用するアルゴリズムです。1対多/多対多のリレーショナルデータは同時に取得されているときに内部で使用されます。SQLの結果セットの列の代わりにレコードの数を制限するためにこの種の特別なアルゴリズムはLIMIT句に必要です。
このビヘイビアは設定システムを使用してオーバーライドできます(グローバル、接続もしくはテーブルレベル):
$table->setAttribute(Doctrine_Core::ATTR_QUERY_LIMIT,
Doctrine_Core::LIMIT_ROWS); $table->setAttribute(Doctrine_Core::ATTR_QUERY_LIMIT, Doctrine_Core::LIMIT_RECORDS); // リバート
次の例ではユーザーと電話番号がありこれらのリレーションは1対多です。最初の20ユーザーを取得しすべての関連する電話番号を取得することを考えてみましょう。
クエリの最後でシンプルなドライバ固有のLIMIT 20を追加すれば正しい結果が返されるとお考えの方がいらっしゃるかもしれません。これは間違っています。1から20までの任意のユーザーを20の電話番号を持つ最初のユーザーとして取得しレコードセットが20の列で構成されることがあるからです。
DQLはサブクエリと複雑だが効率的なサブクエリの解析でこの問題に打ち勝ちます。次の例では最初の20人のユーザーとそのすべての電話番号を効果的な1つのクエリで取得しようとしています。DQLパーサーがサブクエリでもカラム集約継承を使うほど賢くまたエイリアスの衝突を回避するサブクエリのテーブルに異なるテーブルを使うほど賢いことに注目してください。
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id, u.username, p.*') ->from('User u') ->leftJoin('u.Phonenumbers p') ->limit(20);
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, u.username AS uusername, p.id AS p**id,
p.user_id AS p**user_id, p.phonenumber AS p__phonenumber FROM user u LEFT JOIN phonenumber p ON u.id = p.user_id
次の例では最初の20人のユーザーとすべての電話番号かつ実際に電話番号を持つユーザーのみを1つの効率的なクエリで取得します。これは`INNER
JOIN``を使います。サブクエリで``INNER
JOIN
`を使うほどDQLパーサーが賢いことに注目してください:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id, u.username, p.*') ->from('User u') ->innerJoin('u.Phonenumbers p') ->limit(20);
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, u.username AS uusername, p.id AS p**id,
p.user_id AS p**user_id, p.phonenumber AS p__phonenumber FROM user u INNER JOIN phonenumber p ON u.id = p.user_id
名前付きクエリ
変化する可能性があるモデルを扱うが、クエリを簡単に更新できるようにする必要があるとき、クエリを定義する簡単な方法を見つける必要があります。例えば1つのフィールドを変更して何も壊れていないことを確認するためにアプリケーションのすべてのクエリを追跡する必要がある状況を想像してください。
名前付きクエリはこの状況を解決する素晴らしく効率的な方法です。これによってDoctrine_Queries
を作成しこれらを書き直すこと無く再利用できるようになります。
名前付きクエリのサポートはDoctrine\_Query\_Registry
のサポートの上で構築されます。Doctrine\_Query_Registry
はクエリを登録して名前をつけるためのクラスです。これはアプリケーションクエリの編成を手助けしこれに沿ってとても便利な機能を提供します。
レジストリオブジェクトのadd()
メソッドを使用してこのクエリは追加されます。これは2つのパラメータ、クエリの名前と実際のDQLクエリを受け取ります。
// test.php
// ... $r = Doctrine_Manager::getInstance()->getQueryRegistry();
$r->add('User/all', 'FROM User u');
$userTable = Doctrine_Core::getTable('User');
// すべてのユーザーを見つける $users = $userTable->find('all');
このサポートを簡略化するために、Doctrine\_Table
はDoctrine\_Query_Registry
へのアクセサをサポートします。
名前付きクエリを作成する
trueとして定義されたgenerateTableClasses
オプションでモデルをビルドするとき、それぞれのレコードクラスはDoctrine_Table
を継承する\*Table
クラスも生成します。
それから、名前付きクエリを含めるためにconstruct()
メソッドを実装できます:
class UserTable extends Doctrine_Table { public function construct() {
// DQL文字列を使用して定義されたNamed Query $this->addNamedQuery('get.by.id', 'SELECT u.username FROM User u WHERE u.id = ?');
// Doctrine_Queryオブジェクトを使用して定義された名前付きのクエリ
$this->addNamedQuery(
'get.by.similar.usernames', Doctrine_Query::create()
->select('u.id, u.username')
->from('User u')
->where('LOWER(u.username) LIKE LOWER(?)')
);
}
}
名前付きクエリにアクセスする
Doctrine_Table
のサブクラスであるMyFooTable
クラスにリーチするには、次のようにできます:
$userTable = Doctrine_Core::getTable('User');
名前付きクエリにアクセスするには(常にDoctrine_Query
インスタンスを返す):
$q = $userTable->createNamedQuery('get.by.id');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS uid, u.username AS uusername FROM user u WHERE u.id
= ?
名前付きクエリを実行する
名前付きクエリを実行するには2つの方法があります。1つめの方法は通常のインスタンスとしてDoctrine_Query
を読み取り通常通りに実行します:
// test.php
// ... $users = Doctrine_Core::getTable('User') ->createNamedQuery('get.by.similar.usernames') ->execute(array('%jon%wage%'));
次のようにも実行を簡略化できます:
// test.php
// ... $users = Doctrine_Core::getTable('User') ->find('get.by.similar.usernames', array('%jon%wage%'));
find()
メソッドはハイドレーションモード用の3番目の引数を受け取ります。
名前付きクエリにクロスアクセスする
それで十分でなければ、DoctrineはDoctrine\_Query_Registry
を利用しオブジェクト間の名前付きクエリにクロスアクセスできるようにする名前空間クエリを使います。Article
レコードの\*Table
クラスのインスタンスがあることを想定します。User
レコードの"get.by.id"
名前付きクエリを呼び出したいとします。名前付きクエリにアクセスするには、次のように行わなければなりません:
// test.php
// ... $articleTable = Doctrine_Core::getTable('Article');
$users = $articleTable->find('User/get.by.id', array(1, 2, 3));
BNF
QL_statement ::= select_statement | update_statement |
delete_statement select_statement ::= select_clause from_clause [where_clause] [groupby_clause] [having_clause] [orderby_clause] update_statement ::= update_clause [where_clause] delete_statement ::= delete_clause [where_clause] from_clause ::= FROM identification_variable_declaration {, {identification_variable_declaration | collection_member_declaration``* identification_variable_declaration ::= range_variable_declaration { join | fetch_join }* range_variable_declaration ::= abstract_schema_name [AS ] identification_variable join ::= join_spec join_association_path_expression [AS ] identification_variable fetch_join ::= join_specFETCH join_association_path_expression association_path_expression ::= collection_valued_path_expression | single_valued_association_path_expression join_spec::= [LEFT [OUTER ] |INNER ]JOIN join_association_path_expression ::= join_collection_valued_path_expression | join_single_valued_association_path_expression join_collection_valued_path_expression::= identification_variable.collection_valued_association_field join_single_valued_association_path_expression::= identification_variable.single_valued_association_field collection_member_declaration ::= IN ( collection_valued_path_expression) [AS ] identification_variable single_valued_path_expression ::= state_field_path_expression | single_valued_association_path_expression state_field_path_expression ::= {identification_variable | single_valued_association_path_expression}.state_field single_valued_association_path_expression ::= identification_variable.{single_valued_association_field.}* single_valued_association_field collection_valued_path_expression ::= identification_variable.{single_valued_association_field.}collection\_valued\_association\_field state\_field ::= {embedded\_class\_state\_field.}simple_state_field update_clause ::=UPDATE abstract_schema_name [[AS ] identification_variable] SET update_item {, update_item}* update_item ::= [identification_variable.]{state_field | single_valued_association_field} = new_value new_value ::= simple_arithmetic_expression | string_primary | datetime_primary |
boolean_primary | enum_primary simple_entity_expression | NULL delete_clause ::=DELETE FROM abstract_schema_name [[AS ] identification_variable] select_clause ::=SELECT [DISTINCT ] select_expression {, select_expression}* select_expression ::= single_valued_path_expression | aggregate_expression | identification_variable | OBJECT( identification_variable) | constructor_expression constructor_expression ::= NEW constructor_name( constructor_item {, constructor_item}) constructor\_item ::= single\_valued\_path\_expression \| aggregate\_expression aggregate\_expression ::= {AVG \|MAX \|MIN \|SUM }( [DISTINCT ] state\_field\_path\_expression) \| COUNT ( [DISTINCT ] identification\_variable \| state\_field\_path\_expression \| single\_valued\_association\_path\_expression) where\_clause ::=WHERE conditional\_expression groupby\_clause ::=GROUP BY groupby\_item {, groupby\_item} groupby_item ::= single_valued_path_expression | identification_variable having_clause ::=HAVING conditional_expression orderby_clause ::=ORDER BY orderby_item {, orderby_item}* orderby_item ::= state_field_path_expression [ASC |DESC ] subquery ::= simple_select_clause subquery_from_clause [where_clause] [groupby_clause] [having_clause] subquery_from_clause ::= FROM subselect_identification_variable_declaration {, subselect_identification_variable_declaration}* subselect_identification_variable_declaration ::= identification_variable_declaration | association_path_expression [AS ] identification_variable | collection_member_declaration simple_select_clause ::=SELECT [DISTINCT ] simple_select_expression simple_select_expression::= single_valued_path_expression | aggregate_expression | identification_variable conditional_expression ::= conditional_term | conditional_expressionOR conditional_term conditional_term ::= conditional_factor | conditional_termAND conditional_factor conditional_factor ::= [NOT ] conditional_primary conditional_primary ::= simple_cond_expression |( conditional_expression) simple_cond_expression ::= comparison_expression | between_expression | like_expression | in_expression | null_comparison_expression | empty_collection_comparison_expression |
collection_member_expression | exists_expression between_expression ::= arithmetic_expression [NOT ]BETWEEN arithmetic_expressionAND arithmetic_expression | string_expression [NOT ]BETWEEN string_expressionAND string_expression | datetime_expression [NOT ]BETWEEN datetime_expressionAND datetime_expression in_expression ::= state_field_path_expression [NOT ]IN ( in_item {, in_item}* | subquery) in_item ::= literal | input_parameter like_expression ::= string_expression [NOT ]LIKE pattern_value [ESCAPE escape_character] null_comparison_expression ::= {single_valued_path_expression | input_parameter}IS [NOT ] NULL empty_collection_comparison_expression ::= collection_valued_path_expressionIS [NOT] EMPTY collection_member_expression ::= entity_expression [NOT ]MEMBER [OF ] collection_valued_path_expression exists_expression::= [NOT ]EXISTS (subquery) all_or_any_expression ::= {ALL |ANY |SOME } (subquery) comparison_expression ::= string_expression comparison_operator {string_expression | all_or_any_expression} | boolean_expression {= |<> } {boolean_expression | all_or_any_expression} | enum_expression {= |<> } {enum_expression | all_or_any_expression} | datetime_expression comparison_operator {datetime_expression | all_or_any_expression} | entity_expression {= |<> } {entity_expression | all_or_any_expression} | arithmetic_expression comparison_operator {arithmetic_expression | all_or_any_expression} comparison_operator ::== |> |>= |< |<= |<> arithmetic_expression ::= simple_arithmetic_expression | (subquery) simple_arithmetic_expression ::= arithmetic_term | simple_arithmetic_expression {+ |- } arithmetic_term arithmetic_term ::= arithmetic_factor | arithmetic_term {* |/ } arithmetic_factor arithmetic_factor ::= [{+ |- }] arithmetic_primary arithmetic_primary ::= state_field_path_expression | numeric_literal | (simple_arithmetic_expression) | input_parameter | functions_returning_numerics | aggregate_expression string_expression ::= string_primary | (subquery) string_primary ::= state_field_path_expression | string_literal | input_parameter | functions_returning_strings | aggregate_expression
datetime_expression ::= datetime_primary | (subquery) datetime_primary ::= state_field_path_expression | input_parameter | functions_returning_datetime | aggregate_expression boolean_expression ::= boolean_primary | (subquery) boolean_primary ::= state_field_path_expression | boolean_literal | input_parameter | enum_expression ::= enum_primary | (subquery) enum_primary ::= state_field_path_expression | enum_literal | input_parameter | entity_expression ::= single_valued_association_path_expression | simple_entity_expression simple_entity_expression ::= identification_variable | input_parameter functions_returning_numerics::= LENGTH( string_primary) | LOCATE( string_primary, string_primary[, simple_arithmetic_expression]) | ABS( simple_arithmetic_expression) | SQRT( simple_arithmetic_expression) | MOD( simple_arithmetic_expression, simple_arithmetic_expression) | SIZE( collection_valued_path_expression) functions_returning_datetime ::= CURRENT_DATE | CURRENT_TIME | CURRENT_TIMESTAMP functions_returning_strings ::= CONCAT( string_primary, string_primary) | SUBSTRING( string_primary, simple_arithmetic_expression, simple_arithmetic_expression)| TRIM( [[trim_specification] [trim_character]FROM ] string_primary) | LOWER( string_primary) | UPPER( string_primary) trim_specification ::=LEADING | TRAILING | BOTH
マジックファインダー
Doctrineはモデルに存在する任意のカラムでレコードを見つけることを可能にするDoctrineモデル用のマジックファインダー(magic
finder)を提供します。ユーザーの名前でユーザーを見つけたり、グループの名前でグループを見つけるために役立ちます。通常これはDoctrine_Query
インスタンスを書き再利用できるようにこれをどこかに保存することが必要です。このようなシンプルな状況にはもはや必要ありません。
ファインダーメソッドの基本パターンは次の通りです:
findBy%s(`value)
もしくはfindOneBy%s(`\ value)
です。%s
はカラム名もしくはリレーションのエイリアスです。カラムの名前の場合探す値を提供しなければなりません。リレーションのエイリアスを指定する場合、見つけるリレーションクラスのインスタンスを渡すか、実際の主キーの値を渡すことができます。
最初に扱うUserTable
インスタンスを読み取りましょう:
// test.php
// ... $userTable = Doctrine_Core::getTable('User');
find()
メソッドを利用して主キーでUser
レコードを簡単に見つけられます:
// test.php
// ... $user = $userTable->find(1);
ユーザー名で1人のユーザーを見つけたい場合は次のようにマジックファインダーを使うことができます:
// test.php
// ... $user = $userTable->findOneByUsername('jonwage');
レコード間のリレーションを利用してレコードでユーザーを見つけることができます。User
は複数のPhonenumbers
を持つのでfindBy\*\*()
メソッドにUser
インスタンスを渡すことでこれらのPhonenumber
を見つけることができます:
// test.php
// ... $phonenumberTable = Doctrine::getTable('Phonenumber');
$phonenumbers = phonenumberTable->findByUser(
user);
マジックファインダーはもう少し複雑な検索を可能にします。複数のプロパティによってレコードを検索するためにメソッド名でAnd
とOr
キーワードを使うことができます。
$user = $userTable->findOneByUsernameAndPassword('jonwage',
md5('changeme'));
条件を混ぜることもできます。
$users = $userTable->findByIsAdminAndIsModeratorOrIsSuperAdmin(true,
true, true);
CAUTION これらは語句限られたマジックメソッドの用例で、つねに手書きのDQLクエリで展開することをおすすめします。これらのメソッドはリレーションシップなしの単独のレコードに素早くアクセスするための手段であり、素早くプロトタイプを書くのにもよいものです。
NOTE 上記のマジックファインダーはPHPの`
[http://php.net/\_\_call **call()]``のオーバーロード機能を使うことで作成されます。内在する関数は``Doctrine\_Query``オブジェクトがビルドされる``Doctrine_Table::**call()
`に転送され、実行されてユーザーに返されます。
クエリをデバッグする
Doctrine_Query
オブジェクトはクエリの問題をデバッグするための手助けになる機能を少々提供します:
ときにDoctrine_Query
オブジェクトに対して完全なSQL文字列を見たいことがあります:
// test.php
// ... $q = Doctrine_Query::create() ->select('u.id') ->from('User u') ->orderBy('u.username');
echo $q->getSqlQuery();
getSql()
への呼び出しは次のSQLクエリを出力します:
SELECT u.id AS u__id FROM user u ORDER BY u.username
NOTE 上記の
Doctrine\_Query::getSql()
メソッドによって返されるSQLはトークンをパラメータに置き換えません。これはPDOのジョブでクエリを実行するとき置き換えが実行されるPDOにパラメータを渡します。Doctrine_Query::getParams()
メソッドでパラメータの配列を読み取ることができます。
Doctrine_Query
インスタンス用のパラメータの配列を取得します:
// test.php
// ... print_r($q->getParams());
まとめ
Doctrine Query Languageはこれまでのところ最も高度で役に立つDoctrineの機能です。これによってRDBMSのリレーションからとても複雑なデータを簡単にかつ効率的に選択できます!
これでDoctrineの主要なコンポーネントの使い方を見たので[doc component-overview :name]の章に移りすべてを鳥の目で見ることにします。