merge from yiisoft/yii2

This commit is contained in:
cuileon
2018-11-14 11:57:32 +08:00
parent 47df8a8cc0
commit 489dd119a5
426 changed files with 18465 additions and 4215 deletions

View File

@ -1,7 +1,8 @@
クエリ・ビルダ
==============
[データベース・アクセス・オブジェクト](db-dao.md) の上に構築されているクエリ・ビルダは、SQL クエリをプログラム的に、かつ、DBMS の違いを意識せずに作成することを可能にしてくれます。
[データベース・アクセス・オブジェクト](db-dao.md) の上に構築されているクエリ・ビルダは、SQL クエリをプログラム的に、
かつ、DBMS の違いを意識せずに作成することを可能にしてくれます。
クエリ・ビルダを使うと、生の SQL 文を書くことに比べて、より読みやすい SQL 関連のコードを書き、より安全な SQL 文を生成することが容易になります。
通常、クエリ・ビルダの使用は、二つのステップから成ります。
@ -32,7 +33,9 @@ LIMIT 10
> Info: 通常は、[[yii\db\QueryBuilder]] ではなく、主として [[yii\db\Query]] を使用します。
前者は、クエリ・メソッドの一つを呼ぶときに、後者によって黙示的に起動されます。
[[yii\db\QueryBuilder]] は、DBMS に依存しない [[yii\db\Query]] オブジェクトから、DBMS に依存する SQL 文を生成する (例えば、テーブルやカラムの名前を DBMS ごとに違う方法で引用符で囲む) 役割を負っているクラスです。
[[yii\db\QueryBuilder]] は、DBMS に依存しない [[yii\db\Query]] オブジェクトから、DBMS に依存する SQL 文を生成する
(例えば、テーブルやカラムの名前を DBMS ごとに違う方法で引用符で囲む) 役割を負っているクラスです。
## クエリを構築する <span id="building-queries"></span>
@ -80,8 +83,7 @@ $query->select(['user_id' => 'user.id', 'email']);
すなわち、*全て* のカラムが選択されることになります。
カラム名に加えて、DB 式をセレクトすることも出来ます。
カンマを含む DB 式をセレクトする場合は、自動的に引用符で囲む機能が誤動作しないように、配列形式を使わなければなりません。
例えば、
カンマを含む DB 式をセレクトする場合は、自動的に引用符で囲む機能が誤動作しないように、配列形式を使わなければなりません。例えば、
```php
$query->select(["CONCAT(first_name, ' ', last_name) AS full_name", 'email']);
@ -91,8 +93,7 @@ $query->select(["CONCAT(first_name, ' ', last_name) AS full_name", 'email']);
[特定のデータベースに依存しない引用符の構文](db-dao.md#quoting-table-and-column-names) を使うことが出来ます。
バージョン 2.0.1 以降では、サブ・クエリもセレクトすることが出来ます。
各サブ・クエリは、[[yii\db\Query]] オブジェクトの形で指定しなければなりません。
例えば、
各サブ・クエリは、[[yii\db\Query]] オブジェクトの形で指定しなければなりません。例えば、
```php
$subQuery = (new Query())->select('COUNT(*)')->from('user');
@ -108,6 +109,13 @@ $query = (new Query())->select(['id', 'count' => $subQuery])->from('post');
$query->select('user_id')->distinct();
```
追加のカラムをセレクトするためには [[yii\db\Query::addSelect()|addSelect()]] を呼ぶことが出来ます。例えば、
```php
$query->select(['id', 'username'])
->addSelect(['email']);
```
### [[yii\db\Query::from()|from()]] <span id="from"></span>
@ -159,12 +167,10 @@ $query->from(['u' => $subQuery]);
- 演算子形式、例えば、`['like', 'name', 'test']`
- オブジェクト形式、例えば、`new LikeCondition('name', 'LIKE', 'test')`
#### 文字列形式 <span id="string-format"></span>
文字列形式は、非常に単純な条件を定義する場合や、DBMS の組み込み関数を使う必要がある場合に最適です。
これは、生の SQL を書いている場合と同じように動作します。
例えば、
これは、生の SQL を書いている場合と同じように動作します。例えば、
```php
$query->where('status=1');
@ -184,7 +190,8 @@ $query->where('YEAR(somedate) = 2015');
$query->where("status=$status");
```
`パラメータ・バインディング` を使う場合は、[[yii\db\Query::params()|params()]] または [[yii\db\Query::addParams()|addParams()]] を使って、パラメータの指定を分離することが出来ます。
`パラメータ・バインディング` を使う場合は、[[yii\db\Query::params()|params()]] または [[yii\db\Query::addParams()|addParams()]]
を使って、パラメータの指定を分離することが出来ます。
```php
$query->where('status=:status')
@ -194,7 +201,6 @@ $query->where('status=:status')
生の SQL が使われる場所ではどこでもそうですが、文字列形式で条件を書く場合には、テーブルやカラムの名前を表すために
[特定のデータベースに依存しない引用符の構文](db-dao.md#quoting-table-and-column-names) を使うことが出来ます。
#### ハッシュ形式 <span id="hash-format"></span>
値が等しいことを要求する単純な条件をいくつか `AND` で結合する場合は、ハッシュ形式を使うのが最適です。
@ -222,8 +228,7 @@ $query->where(['id' => $userQuery]);
```
ハッシュ形式を使う場合、Yii は内部的にパラメータ・バインディングを使用します。
従って、[文字列形式](#string-format) とは対照的に、ここでは手動でパラメータを追加する必要はありません。
ただし、Yii はカラム名を決してエスケープしないことに注意して下さい。
従って、[文字列形式](#string-format) とは対照的に、ここでは手動でパラメータを追加する必要はありません。ただし、Yii はカラム名を決してエスケープしないことに注意して下さい。
従って、ユーザから取得した変数を何ら追加のチェックをすることなくカラム名として渡すと、SQL インジェクション攻撃に対して脆弱になります。
アプリケーションを安全に保つためには、カラム名として変数を使わないこと、または、変数をホワイト・リストによってフィルターすることが必要です。
カラム名をユーザから取得する必要がある場合は、ガイドの [データをフィルタリングする](output-data-widgets.md#filtering-data) という記事を読んで下さい。
@ -232,15 +237,14 @@ $query->where(['id' => $userQuery]);
```php
// 脆弱なコード:
$column = $request->get('column');
$value = $request->get('value);
$value = $request->get('value');
$query->where([$column => $value]);
// $value は安全です。しかし、$column の名前はエンコードされません。
```
#### 演算子形式 <span id="operator-format"></span>
演算子形式を使うと、任意の条件をプログラム的な方法で指定することが出来ます。
これは次の形式を取るものです。
演算子形式を使うと、任意の条件をプログラム的な方法で指定することが出来ます。これは次の形式を取るものです。
```php
[演算子, オペランド1, オペランド2, ...]
@ -249,24 +253,25 @@ $query->where([$column => $value]);
ここで、各オペランドは、文字列形式、ハッシュ形式、あるいは、再帰的に演算子形式として指定することが出来ます。
そして、演算子には、次のどれか一つを使うことが出来ます。
- `and`: 複数のオペランドが `AND` を使って結合されます。例えば、`['and', 'id=1', 'id=2']``id=1 AND id=2` を生成します。
- `and`: 複数のオペランドが `AND` を使って結合されます。
例えば、`['and', 'id=1', 'id=2']``id=1 AND id=2` を生成します。
オペランドが配列である場合は、ここで説明されている規則に従って文字列に変換されます。
例えば、`['and', 'type=1', ['or', 'id=1', 'id=2']]``type=1 AND (id=1 OR id=2)` を生成します。
このメソッドは、文字列を引用符で囲ったりエスケープしたりしません。
- `or`: オペランドが `OR` を使って結合されること以外は `and` 演算子と同じです。
- `not`: オペランド1 だけを受け取って `NOT()` で包みます。例えば、`['not', 'id=1']``NOT (id=1)` を生成します。
オペランド1 は、それ自体も複数の式を表す配列であっても構いません。例えば、`['not', ['status' => 'draft', 'name' => 'example']]``NOT ((status='draft') AND (name='example'))` を生成します。
- `not`: オペランド1 だけを受け取って `NOT()` で包みます。例えば、`['not', 'id=1']``NOT (id=1)` を生成します。オペランド1 は、それ自体も複数の式を表す配列であっても構いません。例えば、`['not', ['status' => 'draft', 'name' => 'example']]``NOT ((status='draft') AND (name='example'))` を生成します。
- `between`: オペランド 1 はカラム名、オペランド 2 と 3 はカラムの値が属すべき範囲の開始値と終了値としなければなりません。
例えば、`['between', 'id', 1, 10]``id BETWEEN 1 AND 10` を生成します。
値が二つのカラムの値の間にあるという条件 (例えば、`11 BETWEEN min_id AND max_id`) を構築する必要がある場合は、
[[yii\db\conditions\BetweenColumnsCondition|BetweenColumnsCondition]] を使用しなければなりません。
条件定義のオブジェクト形式について更に学習するためには [条件 – オブジェクト形式](#object-format) のセクションを参照して下さい。
条件定義のオブジェクト形式について更に学習するためには [条件 – オブジェクト形式](#object-format)
のセクションを参照して下さい。
- `not between`: 生成される条件において `BETWEEN``NOT BETWEEN` に置き換えられる以外は、`between` と同じです。
- `not between`: 生成される条件において `BETWEEN``NOT BETWEEN` に置き換えられる以外は、
`between` と同じです。
- `in`: オペランド 1 はカラム名または DB 式でなければなりません。
オペランド 2 は、配列または `Query` オブジェクトのどちらかを取ることが出来ます。
@ -279,23 +284,30 @@ $query->where([$column => $value]);
- `not in`: 生成される条件において `IN``NOT IN` に置き換えられる以外は、`in` と同じです。
- `like`: オペランド 1 はカラム名または DB 式、オペランド 2 はカラムまたは DB 式がマッチすべき値を示す文字列または配列でなければなりません。
- `like`: オペランド 1 はカラム名または DB 式、オペランド 2 はそのカラムまたは DB 式がマッチすべき値を示す
文字列または配列でなければなりません。
例えば、`['like', 'name', 'tester']``name LIKE '%tester%'` を生成します。
値域が配列として与えられた場合は、複数の `LIKE` 述語が生成されて 'AND' によって結合されます。
例えば、`['like', 'name', ['test', 'sample']]` `name LIKE '%test%' AND name LIKE '%sample%'` を生成します。
例えば、`['like', 'name', ['test', 'sample']]`
`name LIKE '%test%' AND name LIKE '%sample%'` を生成します。
さらに、オプションである三番目のオペランドによって、値の中の特殊文字をエスケープする方法を指定することも出来ます。
このオペランド 3 は、特殊文字とそのエスケープ結果のマッピングを示す配列でなければなりません。
このオペランドが提供されない場合は、デフォルトのエスケープマッピングが使用されます。
このオペランドが提供されない場合は、デフォルトのエスケープマッピングが使用されます。
`false` または空の配列を使って、値が既にエスケープ済みであり、それ以上エスケープを適用すべきでないことを示すことが出来ます。
エスケープマッピングを使用する場合 (または第三のオペランドが与えられない場合) は、値が自動的に一組のパーセント記号によって囲まれることに注意してください。
エスケープマッピングを使用する場合 (または第三のオペランドが与えられない場合) は、
値が自動的に一対のパーセント記号によって囲まれることに注意してください。
> Note: PostgreSQL を使っている場合は、`like` の代りに、大文字と小文字を区別しない比較のための [`ilike`](http://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE) を使うことも出来ます。
> Note: PostgreSQL を使っている場合は、`like` の代りに、大文字と小文字を区別しない比較のための
> [`ilike`](http://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE) を使うことも出来ます。
- `or like`: オペランド 2 が配列である場合に `LIKE` 述語が `OR` によって結合される以外は、`like` 演算子と同じです。
- `or like`: オペランド 2 が配列である場合に `LIKE` 述語が `OR` によって結合される以外は、
`like` 演算子と同じです。
- `not like`: 生成される条件において `LIKE``NOT LIKE` に置き換えられる以外は、`like` 演算子と同じです。
- `not like`: 生成される条件において `LIKE``NOT LIKE` に置き換えられる以外は、
`like` 演算子と同じです。
- `or not like`: `NOT LIKE` 述語が `OR` によって結合される以外は、`not like` 演算子と同じです。
- `or not like`: `NOT LIKE` 述語が `OR` によって結合される以外は、
`not like` 演算子と同じです。
- `exists`: 要求される一つだけのオペランドは、サブ・クエリを表す [[yii\db\Query]] のインスタンスでなければなりません。
これは `EXISTS (sub-query)` という式を構築します。
@ -305,10 +317,9 @@ $query->where([$column => $value]);
- `>``<=`、その他、二つのオペランドを取る有効な DB 演算子全て: 最初のオペランドはカラム名、第二のオペランドは値でなければなりません。
例えば、`['>', 'age', 10]``age>10` を生成します。
演算子形式を使う場合、Yii は内部的にパラメータ・バインディングを使用します。
演算子形式を使う場合、Yii は値に対して内部的にパラメータ・バインディングを使用します。
従って、[文字列形式](#string-format) とは対照的に、ここでは手動でパラメータを追加する必要はありません。
ただし、Yii はカラム名を決してエスケープしないことに注意して下さい。
従って、ユーザから取得した変数を何ら追加のチェックをすることなくカラム名として渡すと、SQL インジェクション攻撃に対して脆弱になります。
ただし、Yii はカラム名を決してエスケープしないことに注意して下さい。従って、変数をカラム名として渡すと、アプリケーションは SQL インジェクション攻撃に対して脆弱になります。
アプリケーションを安全に保つためには、カラム名として変数を使わないこと、または、変数をホワイト・リストによってフィルターすることが必要です。
カラム名をユーザから取得する必要がある場合は、ガイドの [データをフィルタリングする](output-data-widgets.md#filtering-data) という記事を読んで下さい。
例えば、次のコードは脆弱です。
@ -324,9 +335,11 @@ $query->where([$column => $value]);
#### オブジェクト形式 <span id="object-format"></span>
オブジェクト形式は 2.0.14 から利用可能な、条件を定義するための最も強力でもあり、最も複雑でもある方法です。
クエリ・ビルダの上にあなた自身の抽象レイヤを構築したい、または独自の複雑な条件を実装したいかする場合には、この形式を採用する必要があります。
クエリ・ビルダの上にあなた自身の抽象レイヤを構築したいときや、または独自の複雑な条件を実装したいときは、
この形式を採用する必要があります。
条件クラスのインスタンスはイミュータブルです。条件クラスのインスタンスは条件データを保持し、条件ビルダにゲッターを提供することを唯一の目的とします。
条件クラスのインスタンスはイミュータブルです。
条件クラスのインスタンスは条件データを保持し、条件ビルダにゲッターを提供することを唯一の目的とします。
そして、条件ビルダが、条件クラスのインスタンスに保存されたデータを SQL の式に変換するロジックを持つクラスです。
内部的には、上述の三つの形式は、生の SQL を構築するに先立って、暗黙のうちにオブジェクト形式に変換されます。
@ -340,8 +353,9 @@ $query->andWhere(new OrCondition([
]))
```
演算子形式からオブジェクト形式への変換は、演算子の名前とそれを表すクラス名を対応づける
[[yii\db\QueryBuilder::conditionClasses|QueryBuilder::conditionClasses]] プロパティに従って行われます。
演算子形式からオブジェクト形式への変換は、
演算子の名前とそれを表すクラス名を対応づける [[yii\db\QueryBuilder::conditionClasses|QueryBuilder::conditionClasses]]
プロパティに従って行われます。
- `AND`, `OR` -> `yii\db\conditions\ConjunctionCondition`
- `NOT` -> `yii\db\conditions\NotCondition`
@ -360,8 +374,6 @@ $query->andWhere(new OrCondition([
これらのメソッドを複数回呼んで、複数の条件を別々に追加することが出来ます。
例えば、
条件の一部を動的に構築しようとする場合は、`andWhere()``orWhere()` を使うのが非常に便利です。
```php
$status = 10;
$search = 'yii';
@ -379,10 +391,12 @@ if (!empty($search)) {
WHERE (`status` = 10) AND (`title` LIKE '%yii%')
```
#### フィルタ条件 <span id="filter-conditions"></span>
ユーザの入力に基づいて `WHERE` の条件を構築する場合、普通は、空の入力値は無視したいものです。
例えば、ユーザ名とメール・アドレスによる検索が可能な検索フォームにおいては、ユーザが username/email のインプット・フィールドに何も入力しなかった場合は、username/email の条件を無視したいでしょう。
例えば、ユーザ名とメール・アドレスによる検索が可能な検索フォームにおいては、
ユーザが username/email のインプット・フィールドに何も入力しなかった場合は、username/email の条件を無視したいでしょう。
[[yii\db\Query::filterWhere()|filterWhere()]] メソッドを使うことによって、この目的を達することが出来ます。
```php
@ -393,14 +407,18 @@ $query->filterWhere([
]);
```
[[yii\db\Query::filterWhere()|filterWhere()]] と [[yii\db\Query::where()|where()]] の唯一の違いは、前者は [ハッシュ形式](#hash-format) の条件において提供された空の値を無視する、という点です。
[[yii\db\Query::filterWhere()|filterWhere()]] と [[yii\db\Query::where()|where()]] の唯一の違いは、
前者は [ハッシュ形式](#hash-format) の条件において提供された空の値を無視する、という点です。
従って、`$email` が空で `$sername` がそうではない場合は、上記のコードは、結果として `WHERE username=:username` という SQL 条件になります。
> Info: 値が空であると見なされるのは、`null`、空の配列、空の文字列、または空白のみを含む文字列である場合です。
[[yii\db\Query::andWhere()|andWhere()]] または [[yii\db\Query::orWhere()|orWhere()]] と同じように、[[yii\db\Query::andFilterWhere()|andFilterWhere()]] または [[yii\db\Query::orFilterWhere()|orFilterWhere()]] を使って、既存の条件に別のフィルタ条件を追加することも出来ます。
[[yii\db\Query::andWhere()|andWhere()]] または [[yii\db\Query::orWhere()|orWhere()]] と同じように、
[[yii\db\Query::andFilterWhere()|andFilterWhere()]] または [[yii\db\Query::orFilterWhere()|orFilterWhere()]] を使って、
既存の条件に別のフィルタ条件を追加することも出来ます。
さらに加えて、値の方に含まれている比較演算子を適切に判断してくれる [[yii\db\Query::andFilterCompare()]] があります。
さらに加えて、値の方に含まれている比較演算子を適切に判断してくれる
[[yii\db\Query::andFilterCompare()]] があります。
```php
$query->andFilterCompare('name', 'John Doe');
@ -408,7 +426,7 @@ $query->andFilterCompare('rating', '>9');
$query->andFilterCompare('value', '<=100');
```
比較演算子を明示的に指定することも可能です。
演算子を明示的に指定することも可能です。
```php
$query->andFilterCompare('name', 'Doe', 'like');
@ -424,7 +442,6 @@ Yii 2.0.11 以降には、`HAVING` の条件のためにも、同様のメソッ
[[yii\db\Query::orderBy()|orderBy()]] メソッドは SQL クエリの `ORDER BY` 句を指定します。例えば、
```php
// ... ORDER BY `id` ASC, `name` DESC
$query->orderBy([
@ -453,10 +470,10 @@ $query->orderBy('id ASC')
->addOrderBy('name DESC');
```
### [[yii\db\Query::groupBy()|groupBy()]] <span id="group-by"></span>
[[yii\db\Query::groupBy()|groupBy()]] メソッドは SQL クエリの `GROUP BY` 句を指定します。
例えば、
[[yii\db\Query::groupBy()|groupBy()]] メソッドは SQL クエリの `GROUP BY` 句を指定します。例えば、
```php
// ... GROUP BY `id`, `status`
@ -484,8 +501,7 @@ $query->groupBy(['id', 'status'])
### [[yii\db\Query::having()|having()]] <span id="having"></span>
[[yii\db\Query::having()|having()]] メソッドは SQL クエリの `HAVING` 句を指定します。
このメソッドが取る条件は、[where()](#where) と同じ方法で指定することが出来ます。
例えば、
このメソッドが取る条件は、[where()](#where) と同じ方法で指定することが出来ます。例えば、
```php
// ... HAVING `status` = 1
@ -516,7 +532,8 @@ $query->limit(10)->offset(20);
無効な上限やオフセット (例えば、負の数) を指定した場合は、無視されます。
> Info: `LIMIT` と `OFFSET` をサポートしていない DBMS (例えば MSSQL) に対しては、クエリ・ビルダが `LIMIT`/`OFFSET` の振る舞いをエミュレートする SQL 文を生成します。
> Info: `LIMIT` と `OFFSET` をサポートしていない DBMS (例えば MSSQL) に対しては、
クエリ・ビルダが `LIMIT`/`OFFSET` の振る舞いをエミュレートする SQL 文を生成します。
### [[yii\db\Query::join()|join()]] <span id="join"></span>
@ -539,7 +556,7 @@ $query->join('LEFT JOIN', 'post', 'post.user_id = user.id');
配列記法ではなく文字列記法を使って、`'user.id = comment.userId'` という条件を指定しなければなりません。
- `$params`: オプション。結合条件にバインドされるパラメータ。
`INNER JOIN`、`LEFT JOIN` および `RIGHT JOIN` を指定するためには、それぞれ、次のショートカットメソッドを使うことが出来ます。
`INNER JOIN`、`LEFT JOIN` および `RIGHT JOIN` を指定するためには、それぞれ、次のショートカットメソッドを使うことが出来ます。
- [[yii\db\Query::innerJoin()|innerJoin()]]
- [[yii\db\Query::leftJoin()|leftJoin()]]
@ -554,8 +571,7 @@ $query->leftJoin('post', 'post.user_id = user.id');
複数のテーブルを結合するためには、テーブルごとに一回ずつ、上記の結合メソッドを複数回呼び出します。
テーブルを結合することに加えて、サブ・クエリを結合することも出来ます。
そうするためには、結合されるべきサブ・クエリを [[yii\db\Query]] オブジェクトとして指定します。
例えば、
そうするためには、結合されるべきサブ・クエリを [[yii\db\Query]] オブジェクトとして指定します。例えば、
```php
$subQuery = (new \yii\db\Query())->from('post');
@ -619,9 +635,8 @@ $row = (new \yii\db\Query())
> Note: [[yii\db\Query::one()|one()]] メソッドはクエリ結果の最初の行だけを返します。このメソッドは `LIMIT 1`
を生成された SQL 文に追加しません。このことは、クエリが一つまたは少数の行しか返さないことが判っている場合
(例えば、何らかのプライマリ・キーでクエリを発行する場合) は問題になりませんし、むしろ好ましいことです。
しかし、クエリ結果が多数のデータ行になる可能性がある場合は、パフォーマンスを向上させるために、
明示的に `limit(1)` を呼部べきです。例えば、
`(new \yii\db\Query())->from('user')->limit(1)->one()` のように。
しかし、クエリ結果が多数のデータ行になる可能性がある場合は、パフォーマンスを向上させるために、明示的に `limit(1)` を呼ぶべきです。例えば、
`(new \yii\db\Query())->from('user')->limit(1)->one()`
上記のメソッドの全ては、オプションで、DB クエリの実行に使用されるべき [[yii\db\Connection|DB 接続]] を表す `$db` パラメータを取ることが出来ます。
このパラメータを省略した場合は、DB 接続として `db` [アプリケーション・コンポーネント](structure-application-components.md) が使用されます。
@ -667,8 +682,7 @@ $rows = $command->queryAll();
[[yii\db\Query::all()|all()]] を呼ぶと、結果の行は連続した整数でインデックスされた配列で返されます。
場合によっては、違う方法でインデックスしたいことがあるでしょう。
例えば、特定のカラムの値や、何らかの式の値によってインデックスするなどです。
この目的は、[[yii\db\Query::all()|all()]] の前に [[yii\db\Query::indexBy()|indexBy()]] を呼ぶことによって達成することが出来ます。
例えば、
この目的は、[[yii\db\Query::all()|all()]] の前に [[yii\db\Query::indexBy()|indexBy()]] を呼ぶことによって達成することが出来ます。例えば、
```php
// [100 => ['id' => 100, 'username' => '...', ...], 101 => [...], 103 => [...], ...] を返す
@ -689,11 +703,11 @@ $query = (new \yii\db\Query())
})->all();
```
この無名関数は、現在の行データを含む `$row` というパラメータを取り、現在の行のインデックス値として使われるスカラ値を返さなくてはなりません。
この無名関数は、現在の行データを含む `$row` というパラメータを取り、
現在の行のインデックス値として使われるスカラ値を返さなくてはなりません。
> Note: [[yii\db\Query::groupBy()|groupBy()]] や [[yii\db\Query::orderBy()|orderBy()]]
> のようなクエリ・メソッドが SQL に変換されてクエリの一部となるのとは対照的に、
> このメソッドはデータベースからデータが取得された後で動作します。
> のようなクエリ・メソッドが SQL に変換されてクエリの一部となるのとは対照的に、このメソッドはデータベースからデータが取得された後で動作します。
> このことは、クエリの SELECT に含まれるカラム名だけを使うことが出来る、ということを意味します。
> また、テーブル・プレフィックスを付けてカラムを選択した場合、例えば `customer.id` を選択した場合は、
> リザルトセットのカラム名は `id` しか含みませんので、テーブルプレフィックス無しで `->indexBy('id')` と呼ぶ必要があります。
@ -703,8 +717,8 @@ $query = (new \yii\db\Query())
大量のデータを扱う場合は、[[yii\db\Query::all()]] のようなメソッドは適していません。
なぜなら、それらのメソッドは、クエリの結果全てをクライアントのメモリに読み込むことを必要とするためです。
この問題を解決するために、Yii はバッチ・クエリのサポートを提供しています。
エリ結果はサーバに保持し、クライアントはカーソルを利用して1回に1バッチずつ結果セットを反復取得するのです。
この問題を解決するために、Yii はバッチ・クエリのサポートを提供しています。クエリ結果はサーバに保持し、
クライアントはカーソルを利用して1回に1バッチずつ結果セットを反復取得するのです。
> Warning: MySQL のバッチ・クエリの実装には既知の制約と回避策があります。下記を参照して下さい。
@ -728,15 +742,16 @@ foreach ($query->each() as $user) {
}
```
[[yii\db\Query::batch()]] メソッドと [[yii\db\Query::each()]] メソッドは [[yii\db\BatchQueryResult]] オブジェクトを返します。
このオブジェクトは `Iterator` インタフェイスを実装しており、従って、`foreach` 構文の中で使うことが出来ます。
[[yii\db\Query::batch()]] メソッドと [[yii\db\Query::each()]] メソッドは [[yii\db\BatchQueryResult]]
オブジェクトを返します。このオブジェクトは `Iterator` インタフェイスを実装しており、従って、`foreach` 構文の中で使うことが出来ます。
初回の反復の際に、データベースに対する SQL クエリが作成されます。データは、その後、反復のたびにバッチ・モードで取得されます。
デフォルトでは、バッチ・サイズは 100 であり、各バッチにおいて 100 行のデータが取得されます。
バッチ・サイズは、`batch()` または `each()` メソッドに最初のパラメータを渡すことによって変更することが出来ます。
[[yii\db\Query::all()]] とは対照的に、バッチ・クエリは一度に 100 行のデータしかメモリに読み込みません。
[[yii\db\Query::indexBy()]] によって、いずれかのカラムでクエリ結果をインデックスするように指定している場合でも、バッチ・クエリは正しいインデックスを保持します。
[[yii\db\Query::indexBy()]] によって、いずれかのカラムでクエリ結果をインデックスするように指定している場合でも、
バッチ・クエリは正しいインデックスを保持します。
例えば、
@ -756,18 +771,21 @@ foreach ($query->each() as $username => $user) {
#### MySQL におけるバッチ・クエリの制約 <span id="batch-query-mysql"></span>
MySQL のバッチ・クエリの実装は PDO ドライバのライブラリに依存しています。
デフォルトでは、MySQL のクエリは [`バッファ・モード`](http://php.net/manual/ja/mysqlinfo.concepts.buffering.php) で実行されます。
このことが、カーソルを使ってデータを取得する目的を挫折させます。
というのは、バッファ・モードでは、ドライバによって結果セット全体がクライアントのメモリに読み込まれることを防止できないからです。
MySQL のバッチ・クエリの実装は PDO ドライバのライブラリに依存しています。デフォルトでは、MySQL のクエリは
[`バッファ・モード`](http://php.net/manual/ja/mysqlinfo.concepts.buffering.php) で実行されます。
このことが、カーソルを使ってデータを取得する目的を挫折させます。というのは、バッファ・モードでは、
ドライバによって結果セット全体がクライアントのメモリに読み込まれることを防止できないからです。
> Note: `libmysqlclient` が使われている場合 (PHP5 ではそれが普通ですが) は、結果セットに使用されたメモリは PHP のメモリ使用量としてカウントされません。
そのため、一見、バッチ・クエリが正しく動作するように見えますが、実際には、データ・セット全体がクライアントのメモリに読み込まれて、クライアントのメモリを使い果たす可能性があります。
> Note: `libmysqlclient` が使われている場合 (PHP5 ではそれが普通ですが) は、
結果セットに使用されたメモリは PHP のメモリ使用量としてカウントされません。そのため、一見、バッチ・クエリが正しく動作するように見えますが、
実際には、データ・セット全体がクライアントのメモリに読み込まれて、クライアントのメモリを使い果たす可能性があります。
バッファ・モードを無効化してクライアントのメモリ要求量を削減するためには、PDO 接続のプロパティ `PDO::MYSQL_ATTR_USE_BUFFERED_QUERY` を `false` に設定しなければなりません。
しかし、そうすると、データ・セット全体を取得するまでは、同じ接続を通じては別のクエリを実行できなくなります。
これによって `ActiveRecord` が必要に応じてテーブル・スキーマを取得するためのクエリを実行できなくなる可能性があります。
これが問題にならない場合 (テーブル・スキーマが既にキャッシュされている場合) は、元の接続を非バッファ・モードに切り替えて、バッチ・クエリを実行した後に元に戻すということが可能です。
バッファ・モードを無効化してクライアントのメモリ要求量を削減するためには、PDO 接続のプロパティ
`PDO::MYSQL_ATTR_USE_BUFFERED_QUERY` を `false` に設定しなければなりません。しかし、そうすると、
データ・セット全体を取得するまでは、同じ接続を通じては別のクエリを実行できなくなります。これによって
`ActiveRecord` が必要に応じてテーブル・スキーマを取得するためのクエリを実行できなくなる可能性があります。
これが問題にならない場合 (テーブル・スキーマが既にキャッシュされている場合) は、元の接続を非バッファ・モードに切り替えて、
バッチ・クエリを実行した後に元に戻すということが可能です。
```php
Yii::$app->db->pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
@ -777,8 +795,9 @@ Yii::$app->db->pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
Yii::$app->db->pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
```
> Note: MyISAM の場合は、バッチ・クエリが継続している間、テーブルがロックされて、他の接続からの書き込みアクセスが遅延または拒絶されることがあります。
非バッファ・モードのクエリを使う場合は、カーソルを開いている時間を可能な限り短くするように努めて下さい。
> Note: MyISAM の場合は、バッチ・クエリが継続している間、テーブルがロックされて、
他の接続からの書き込みアクセスが遅延または拒絶されることがあります。非バッファ・モードのクエリを使う場合は、
カーソルを開いている時間を可能な限り短くするように努めて下さい。
スキーマがキャッシュされていない場合、またはバッチ・クエリを処理している間に他のクエリを走らせる必要がある場合は、
独立した非バッファ・モードのデータベース接続を作成することが出来ます。
@ -794,10 +813,13 @@ $unbufferedDb->open();
$unbufferedDb->pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
```
`$unbufferedDb` が `PDO::MYSQL_ATTR_USE_BUFFERED_QUERY` が `false` であること以外は、元のバッファ・モードの `$db` と同じ PDO 属性を持つことを保証したい場合は、
[`$db` のディープ・コピー](https://github.com/yiisoft/yii2/issues/8420#issuecomment-301423833) をしてから、手動で false に設定することを考慮して下さい。
`$unbufferedDb` が `PDO::MYSQL_ATTR_USE_BUFFERED_QUERY` が `false` であること以外は、
元のバッファ・モードの `$db` と同じ PDO 属性を持つことを保証したい場合は、
[`$db` のディープ・コピー](https://github.com/yiisoft/yii2/issues/8420#issuecomment-301423833)
をしてから、手動で false に設定することを考慮して下さい。
そして、クエリは普通に作成します。新しい接続を使ってバッチ・クエリを走らせ、結果をバッチで取得、または一つずつ取得します。
そして、クエリは普通に作成します。新しい接続を使ってバッチ・クエリを走らせ、結果をバッチで取得、
または一つずつ取得します。
```php
// データを 1000 のバッチで取得
@ -819,8 +841,8 @@ $unbufferedDb->close();
```
> Note: 非バッファ・モードのクエリは PHP 側でのメモリ消費は少なくなりますが、MySQL サーバの負荷を増加させ得ます。
特別に巨大なデータに対するアプリの動作については、あなた自身のコードを設計することが推奨されます。
例えば、[整数のキーで範囲を分割して、非バッファ・モードのクエリでループする](https://github.com/yiisoft/yii2/issues/8420#issuecomment-296109257) など。
特別に巨大なデータに対するアプリの動作については、あなた自身のコードを設計することが推奨されます。
例えば、[整数のキーで範囲を分割して、非バッファ・モードのクエリでループする](https://github.com/yiisoft/yii2/issues/8420#issuecomment-296109257) など。
### 特製の条件や式を追加する <span id="adding-custom-conditions-and-expressions"></span>
@ -838,8 +860,7 @@ $unbufferedDb->close();
]
```
このような条件を一度に適用できたら良いですね。
一つのクエリの中で複数回使われる場合には、最適化の効果が大きいでしょう。
このような条件を一度に適用できたら良いですね。一つのクエリの中で複数回使われる場合には、最適化の効果が大きいでしょう。
特製の条件オブジェクトを作って、それを実証しましょう。
Yii には、条件を表現するクラスを特徴付ける [[yii\db\conditions\ConditionInterface|ConditionInterface]] があります。
@ -891,29 +912,29 @@ namespace app\db\conditions;
class AllGreaterConditionBuilder implements \yii\db\ExpressionBuilderInterface
{
use \yii\db\Condition\ExpressionBuilderTrait; // コンストラクタと `queryBuilder` プロパティを含む。
use \yii\db\ExpressionBuilderTrait; // コンストラクタと `queryBuilder` プロパティを含む。
/**
* @param AllGreaterCondition $condition ビルドすべき条件
* @param ExpressionInterface $condition ビルドすべき条件
* @param array $params バインディング・パラメータ
* @return AllGreaterCondition
*/
public function build(ConditionInterface $condition, &$params)
public function build(ExpressionInterface $expression, array &$params = [])
{
$value = $condition->getValue();
$conditions = [];
foreach ($condition->getColumns() as $column) {
foreach ($expression->getColumns() as $column) {
$conditions[] = new SimpleCondition($column, '>', $value);
}
return $this->queryBuider->buildCondition(new AndCondition($conditions), $params);
return $this->queryBuilder->buildCondition(new AndCondition($conditions), $params);
}
}
```
後は、単に [[yii\db\QueryBuilder|QueryBuilder]] に私たちの新しい条件について知らせるだけです  
条件のマッピングを `expressionBuilders` 配列に追加します。
次のように、アプリケーション構成で直接に追加することが出来ます。
条件のマッピングを `expressionBuilders` 配列に追加します。次のように、アプリケーション構成で直接に追加することが出来ます。
```php
'db' => [
@ -951,7 +972,8 @@ $query->andWhere(new AllGreaterCondition(['posts', 'comments', 'reactions', 'sub
],
```
そして、`app\db\conditions\AllGreaterCondition` の中で `AllGreaterCondition::fromArrayDefinition()` メソッドの本当の実装を作成します。
そして、`app\db\conditions\AllGreaterCondition` の中で `AllGreaterCondition::fromArrayDefinition()`
メソッドの本当の実装を作成します。
```php
namespace app\db\conditions;
@ -974,13 +996,18 @@ $query->andWhere(['ALL>', ['posts', 'comments', 'reactions', 'subscriptions'], $
```
お気付きのことと思いますが、ここには二つの概念があります。Expression(式)と Condition(条件)です。
[[yii\db\ExpressionInterface]] は、それを構築するために [[yii\db\ExpressionBuilderInterface]] を実装した式ビルダクラスを必要とするオブジェクトを特徴付けるインタフェイスです。
また [[yii\db\condition\ConditionInterface]] は、[[yii\db\ExpressionInterface|ExpressionInterface]] を拡張して、上述されたように配列形式の定義から作成できるオブジェクトに対して使用されるべきものですが、同様にビルダを必要とするものです。
[[yii\db\ExpressionInterface]] は、それを構築するために [[yii\db\ExpressionBuilderInterface]]
を実装した式ビルダクラスを必要とするオブジェクトを特徴付けるインタフェイスです。
また [[yii\db\condition\ConditionInterface]] は、[[yii\db\ExpressionInterface|ExpressionInterface]] を拡張して、
上述されたように配列形式の定義から作成できるオブジェクトに対して使用されるべきものですが、同様にビルダを必要とするものです。
要約すると、
- Expression(式) データセットのためのデータ転送オブジェクトであり、最終的に何らかの SQL 文にコンパイルされる。(演算子、文字列、配列、JSON、等)
- Condition(条件) Expression(式) のスーパーセットで、一つの SQL 条件にコンパイルすることが可能な複数の式(またはスカラ値)の集合。
- Expression(式) データセットのためのデータ転送オブジェクトであり、最終的に何らかの SQL 文にコンパイルされる。
(演算子、文字列、配列、JSON、等)
- Condition(条件) Expression(式) のスーパーセットで、一つの SQL 条件にコンパイルすることが可能な複数の式
(またはスカラ値)の集合。
[[yii\db\ExpressionInterface|ExpressionInterface]] を実装する独自のクラスを作成して、データを SQL 文に変換することの複雑さを隠蔽することが出来ます。
[[yii\db\ExpressionInterface|ExpressionInterface]] を実装する独自のクラスを作成して、
データを SQL 文に変換することの複雑さを隠蔽することが出来ます。
[次の記事](db-active-record.md) では、式について、さらに多くの例を学習します。