mirror of
https://github.com/yiisoft/yii2.git
synced 2025-08-14 22:30:27 +08:00
473 lines
18 KiB
Markdown
473 lines
18 KiB
Markdown
クエリビルダとクエリ
|
|
====================
|
|
|
|
> Note|注意: この節はまだ執筆中です。
|
|
|
|
[データベースの基礎](db-dao.md) の節で説明したように、Yii は基本的なデータベースアクセスレイヤを提供します。
|
|
このデータベースアクセスレイヤは、データベースと相互作用するための低レベルな方法を提供するものです。
|
|
それが有用な状況もありますが、生の SQL を書くことは面倒くさく、間違いを生じやすいものでもあります。
|
|
これに取って代る方法の一つがクエリビルダを使用することです。
|
|
クエリビルダは、実行すべきクエリを生成するためのオブジェクト指向の手法です。
|
|
|
|
クエリビルダの典型的な使用例は以下のようなものです。
|
|
|
|
```php
|
|
$rows = (new \yii\db\Query())
|
|
->select('id, name')
|
|
->from('user')
|
|
->limit(10)
|
|
->all();
|
|
|
|
// これは下記のコードと等価
|
|
|
|
$query = (new \yii\db\Query())
|
|
->select('id, name')
|
|
->from('user')
|
|
->limit(10);
|
|
|
|
// コマンドを作成。$command->sql で実際の SQL を取得できる
|
|
$command = $query->createCommand();
|
|
|
|
// コマンドを実行
|
|
$rows = $command->queryAll();
|
|
```
|
|
|
|
クエリメソッド
|
|
--------------
|
|
|
|
ご覧のように、[[yii\db\Query]] が、あなたが扱わねばならない主役のオブジェクトです。
|
|
舞台裏では、`Query` は、実際には、さまざまなクエリ情報を表現する役目を負っているに過ぎません。
|
|
実際のクエリ構築のロジックは、`createCommand()` コマンドを呼んだときに、[[yii\db\QueryBuilder]] によって実行され、クエリの実行は [[yii\db\Command]] によって実行されます。
|
|
|
|
便宜上の理由から、[[yii\db\Query]] が、よく使われる一連のクエリメソッド (クエリを構築し、実行して、結果を返すメソッド) を提供しています。
|
|
例えば、
|
|
|
|
- [[yii\db\Query::all()|all()]]: クエリを構築し、実行して、全ての結果を配列として返します。
|
|
- [[yii\db\Query::one()|one()]]: 結果の最初の行を返します。
|
|
- [[yii\db\Query::column()|column()]]: 結果の最初のカラムを返します。
|
|
- [[yii\db\Query::scalar()|scalar()]]: 結果の最初の行の最初のカラムを返します。
|
|
- [[yii\db\Query::exists()|exists()]]: 何らかのクエリ結果が有るかどうかを返します。
|
|
- [[yii\db\Query::count()|count()]]: `COUNT` クエリの結果を返します。
|
|
他の似たようなメソッドに、`sum($q)`、`average($q)`、`max($q)`、`min($q)` があり、いわゆる統計データクエリをサポートしています。
|
|
これらのメソッドでは `$q` パラメータは必須であり、カラム名または式を取ります。
|
|
|
|
|
|
クエリを構築する
|
|
----------------
|
|
|
|
以下に、SQL 文の中のさまざまな句を組み立てる方法を説明します。
|
|
話を単純にするために、`$query` という変数を使って [[yii\db\Query]] オブジェクトを表すものとします。
|
|
|
|
|
|
### `SELECT`
|
|
|
|
基本的な `SELECT` クエリを組み立てるためには、どのテーブルからどのカラムをセレクトするかを指定する必要があります。
|
|
|
|
```php
|
|
$query->select('id, name')
|
|
->from('user');
|
|
```
|
|
|
|
セレクトのオプションは、上記のように、カンマで区切られた文字列で指定することも出来ますが、配列によって指定することも出来ます。
|
|
配列を使う構文は、セレクトを動的に組み立てる場合に、特に有用です。
|
|
|
|
```php
|
|
$query->select(['id', 'name'])
|
|
->from('user');
|
|
```
|
|
|
|
> Info|情報: `SELECT` 句が SQL 式を含む場合は、常に配列形式を使うべきです。
|
|
> これは、`CONCAT(first_name, last_name) AS full_name` のように、SQL 式がカンマを含みうるからです。
|
|
> そういう式を他のカラムと一緒に文字列の中に含めると、式がカンマによっていくつかの部分に分離されるおそれがあります。
|
|
> それはあなたの意図するところではないでしょう。
|
|
|
|
カラムを指定するときは、例えば `user.id` や `user.id AS user_id` などのように、テーブル接頭辞やカラムエイリアスを含めることが出来ます。
|
|
カラムを指定するのに配列を使っている場合は、例えば `['user_id' => 'user.id', 'user_name' => 'user.name']` のように、配列のキーを使ってカラムエイリアスを指定することも出来ます。
|
|
|
|
バージョン 2.0.1 以降では、サブクエリをカラムとしてセレクトすることも出来ます。例えば、
|
|
|
|
```php
|
|
$subQuery = (new Query)->select('COUNT(*)')->from('user');
|
|
$query = (new Query)->select(['id', 'count' => $subQuery])->from('post');
|
|
// $query は次の SQL を表現する
|
|
// SELECT `id`, (SELECT COUNT(*) FROM `user`) AS `count` FROM `post`
|
|
```
|
|
|
|
重複行を除外して取得したい場合は、次のように、`distinct()` を呼ぶことが出来ます。
|
|
|
|
```php
|
|
$query->select('user_id')->distinct()->from('post');
|
|
```
|
|
|
|
### `FROM`
|
|
|
|
どのテーブルからデータを取得するかを指定するために `from()` を呼びます。
|
|
|
|
```php
|
|
$query->select('*')->from('user');
|
|
```
|
|
|
|
カンマ区切りの文字列または配列を使って、複数のテーブルを指定することが出来ます。
|
|
テーブル名は、スキーマ接頭辞 (例えば `'public.user'`)、 および/または、テーブルエイリアス (例えば、`'user u'`) を含んでも構いません。
|
|
テーブル名が何らかの括弧を含んでいる場合 (すなわち、テーブルがサブクエリまたは DB 式で与えられていることを意味します) を除いて、メソッドが自動的にテーブル名を引用符で囲みます。
|
|
例えば、
|
|
|
|
```php
|
|
$query->select('u.*, p.*')->from(['user u', 'post p']);
|
|
```
|
|
|
|
テーブルが配列として指定されている場合は、配列のキーをテーブルエイリアスとして使うことも出来ます。
|
|
(テーブルにエイリアスが必要でない場合は、文字列のキーを使わないでください。)
|
|
例えば、
|
|
|
|
```php
|
|
$query->select('u.*, p.*')->from(['u' => 'user', 'p' => 'post']);
|
|
```
|
|
|
|
`Query` オブジェクトを使ってサブクエリを指定することが出来ます。
|
|
この場合、対応する配列のキーがサブクエリのエイリアスとして使われます。
|
|
|
|
```php
|
|
$subQuery = (new Query())->select('id')->from('user')->where('status=1');
|
|
$query->select('*')->from(['u' => $subQuery]);
|
|
```
|
|
|
|
|
|
### `WHERE`
|
|
|
|
通常、データは何らかの基準に基づいて選択されます。
|
|
クエリビルダはその基準を指定するための有用なメソッドをいくつか持っていますが、その中で最も強力なものが `where` です。
|
|
これは多様な方法で使うことが出来ます。
|
|
|
|
条件を適用するもっとも簡単な方法は文字列を使うことです。
|
|
|
|
```php
|
|
$query->where('status=:status', [':status' => $status]);
|
|
```
|
|
|
|
文字列を使うときは、文字列の結合によってクエリを作るのではなく、必ずクエリパラメータをバインドするようにしてください。
|
|
上記の手法は使っても安全ですが、下記の手法は安全ではありません。
|
|
|
|
```php
|
|
$query->where("status=$status"); // 危険!
|
|
```
|
|
|
|
`status` の値をただちにバインドするのでなく、`params` または `addParams` を使ってそうすることも出来ます。
|
|
|
|
```php
|
|
$query->where('status=:status');
|
|
$query->addParams([':status' => $status]);
|
|
```
|
|
|
|
*ハッシュ形式* を使って、複数の条件を同時に `where` にセットすることが出来ます。
|
|
|
|
```php
|
|
$query->where([
|
|
'status' => 10,
|
|
'type' => 2,
|
|
'id' => [4, 8, 15, 16, 23, 42],
|
|
]);
|
|
```
|
|
|
|
上記のコードは次の SQL を生成します。
|
|
|
|
```sql
|
|
WHERE (`status` = 10) AND (`type` = 2) AND (`id` IN (4, 8, 15, 16, 23, 42))
|
|
```
|
|
|
|
NULL はデータベースでは特別な値です。クエリビルダはこれを賢く処理します。例えば、
|
|
|
|
```php
|
|
$query->where(['status' => null]);
|
|
```
|
|
|
|
これは次の WHERE 句になります。
|
|
|
|
```sql
|
|
WHERE (`status` IS NULL)
|
|
```
|
|
|
|
次のように `Query` オブジェクトを使ってサブクエリを作ることも出来ます。
|
|
|
|
```php
|
|
$userQuery = (new Query)->select('id')->from('user');
|
|
$query->where(['id' => $userQuery]);
|
|
```
|
|
|
|
これは次の SQL を生成します。
|
|
|
|
```sql
|
|
WHERE `id` IN (SELECT `id` FROM `user`)
|
|
```
|
|
|
|
このメソッドを使うもう一つの方法は、`[演算子, オペランド1, オペランド2, ...]` という演算形式です。
|
|
Another way to use the method is the operand format which is `[operator, operand1, operand2, ...]`.
|
|
|
|
Operator can be one of the following (see also [[yii\db\QueryInterface::where()]]):
|
|
|
|
- `and`: the operands should be concatenated together using `AND`. For example,
|
|
`['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array,
|
|
it will be converted into a string using the rules described here. For example,
|
|
`['and', 'type=1', ['or', 'id=1', 'id=2']]` will generate `type=1 AND (id=1 OR id=2)`.
|
|
The method will NOT do any quoting or escaping.
|
|
|
|
- `or`: similar to the `and` operator except that the operands are concatenated using `OR`.
|
|
|
|
- `between`: operand 1 should be the column name, and operand 2 and 3 should be the
|
|
starting and ending values of the range that the column is in.
|
|
For example, `['between', 'id', 1, 10]` will generate `id BETWEEN 1 AND 10`.
|
|
|
|
- `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN`
|
|
in the generated condition.
|
|
|
|
- `in`: operand 1 should be a column or DB expression. Operand 2 can be either an array or a `Query` object.
|
|
It will generate an `IN` condition. If Operand 2 is an array, it will represent the range of the values
|
|
that the column or DB expression should be; If Operand 2 is a `Query` object, a sub-query will be generated
|
|
and used as the range of the column or DB expression. For example,
|
|
`['in', 'id', [1, 2, 3]]` will generate `id IN (1, 2, 3)`.
|
|
The method will properly quote the column name and escape values in the range.
|
|
The `in` operator also supports composite columns. In this case, operand 1 should be an array of the columns,
|
|
while operand 2 should be an array of arrays or a `Query` object representing the range of the columns.
|
|
|
|
- `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
|
|
|
|
- `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
|
|
the values that the column or DB expression should be like.
|
|
For example, `['like', 'name', 'tester']` will generate `name LIKE '%tester%'`.
|
|
When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
|
|
using `AND`. For example, `['like', 'name', ['test', 'sample']]` will generate
|
|
`name LIKE '%test%' AND name LIKE '%sample%'`.
|
|
You may also provide an optional third operand to specify how to escape special characters in the values.
|
|
The operand should be an array of mappings from the special characters to their
|
|
escaped counterparts. If this operand is not provided, a default escape mapping will be used.
|
|
You may use `false` or an empty array to indicate the values are already escaped and no escape
|
|
should be applied. Note that when using an escape mapping (or the third operand is not provided),
|
|
the values will be automatically enclosed within a pair of percentage characters.
|
|
|
|
> Note: When using PostgreSQL you may also use [`ilike`](http://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE)
|
|
> instead of `like` for case-insensitive matching.
|
|
|
|
- `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
|
|
predicates when operand 2 is an array.
|
|
|
|
- `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
|
|
in the generated condition.
|
|
|
|
- `or not like`: similar to the `not like` operator except that `OR` is used to concatenate
|
|
the `NOT LIKE` predicates.
|
|
|
|
- `exists`: requires one operand which must be an instance of [[yii\db\Query]] representing the sub-query.
|
|
It will build a `EXISTS (sub-query)` expression.
|
|
|
|
- `not exists`: similar to the `exists` operator and builds a `NOT EXISTS (sub-query)` expression.
|
|
|
|
Additionally you can specify anything as operator:
|
|
|
|
```php
|
|
$userQuery = (new Query)->select('id')->from('user');
|
|
$query->where(['>=', 'id', 10]);
|
|
```
|
|
|
|
It will result in:
|
|
|
|
```sql
|
|
SELECT id FROM user WHERE id >= 10;
|
|
```
|
|
|
|
If you are building parts of condition dynamically it's very convenient to use `andWhere()` and `orWhere()`:
|
|
|
|
```php
|
|
$status = 10;
|
|
$search = 'yii';
|
|
|
|
$query->where(['status' => $status]);
|
|
if (!empty($search)) {
|
|
$query->andWhere(['like', 'title', $search]);
|
|
}
|
|
```
|
|
|
|
In case `$search` isn't empty the following SQL will be generated:
|
|
|
|
```sql
|
|
WHERE (`status` = 10) AND (`title` LIKE '%yii%')
|
|
```
|
|
|
|
#### Building Filter Conditions
|
|
|
|
When building filter conditions based on user inputs, you usually want to specially handle "empty inputs"
|
|
by ignoring them in the filters. For example, you have an HTML form that takes username and email inputs.
|
|
If the user only enters something in the username input, you may want to build a query that only tries to
|
|
match the entered username. You may use the `filterWhere()` method achieve this goal:
|
|
|
|
```php
|
|
// $username and $email are from user inputs
|
|
$query->filterWhere([
|
|
'username' => $username,
|
|
'email' => $email,
|
|
]);
|
|
```
|
|
|
|
The `filterWhere()` method is very similar to `where()`. The main difference is that `filterWhere()`
|
|
will remove empty values from the provided condition. So if `$email` is "empty", the resulting query
|
|
will be `...WHERE username=:username`; and if both `$username` and `$email` are "empty", the query
|
|
will have no `WHERE` part.
|
|
|
|
A value is *empty* if it is null, an empty string, a string consisting of whitespaces, or an empty array.
|
|
|
|
You may also use `andFilterWhere()` and `orFilterWhere()` to append more filter conditions.
|
|
|
|
|
|
### `ORDER BY`
|
|
|
|
For ordering results `orderBy` and `addOrderBy` could be used:
|
|
|
|
```php
|
|
$query->orderBy([
|
|
'id' => SORT_ASC,
|
|
'name' => SORT_DESC,
|
|
]);
|
|
```
|
|
|
|
Here we are ordering by `id` ascending and then by `name` descending.
|
|
|
|
### `GROUP BY` and `HAVING`
|
|
|
|
In order to add `GROUP BY` to generated SQL you can use the following:
|
|
|
|
```php
|
|
$query->groupBy('id, status');
|
|
```
|
|
|
|
If you want to add another field after using `groupBy`:
|
|
|
|
```php
|
|
$query->addGroupBy(['created_at', 'updated_at']);
|
|
```
|
|
|
|
To add a `HAVING` condition the corresponding `having` method and its `andHaving` and `orHaving` can be used. Parameters
|
|
for these are similar to the ones for `where` methods group:
|
|
|
|
```php
|
|
$query->having(['status' => $status]);
|
|
```
|
|
|
|
### `LIMIT` and `OFFSET`
|
|
|
|
To limit result to 10 rows `limit` can be used:
|
|
|
|
```php
|
|
$query->limit(10);
|
|
```
|
|
|
|
To skip 100 fist rows use:
|
|
|
|
```php
|
|
$query->offset(100);
|
|
```
|
|
|
|
### `JOIN`
|
|
|
|
The `JOIN` clauses are generated in the Query Builder by using the applicable join method:
|
|
|
|
- `innerJoin()`
|
|
- `leftJoin()`
|
|
- `rightJoin()`
|
|
|
|
This left join selects data from two related tables in one query:
|
|
|
|
```php
|
|
$query->select(['user.name AS author', 'post.title as title'])
|
|
->from('user')
|
|
->leftJoin('post', 'post.user_id = user.id');
|
|
```
|
|
|
|
In the code, the `leftJoin()` method's first parameter
|
|
specifies the table to join to. The second parameter defines the join condition.
|
|
|
|
If your database application supports other join types, you can use those via the generic `join` method:
|
|
|
|
```php
|
|
$query->join('FULL OUTER JOIN', 'post', 'post.user_id = user.id');
|
|
```
|
|
|
|
The first argument is the join type to perform. The second is the table to join to, and the third is the condition.
|
|
|
|
Like `FROM`, you may also join with sub-queries. To do so, specify the sub-query as an array
|
|
which must contain one element. The array value must be a `Query` object representing the sub-query,
|
|
while the array key is the alias for the sub-query. For example,
|
|
|
|
```php
|
|
$query->leftJoin(['u' => $subQuery], 'u.id=author_id');
|
|
```
|
|
|
|
|
|
### `UNION`
|
|
|
|
`UNION` in SQL adds results of one query to results of another query. Columns returned by both queries should match.
|
|
In Yii in order to build it you can first form two query objects and then use `union` method:
|
|
|
|
```php
|
|
$query = new Query();
|
|
$query->select("id, category_id as type, name")->from('post')->limit(10);
|
|
|
|
$anotherQuery = new Query();
|
|
$anotherQuery->select('id, type, name')->from('user')->limit(10);
|
|
|
|
$query->union($anotherQuery);
|
|
```
|
|
|
|
|
|
Batch Query
|
|
-----------
|
|
|
|
When working with large amount of data, methods such as [[yii\db\Query::all()]] are not suitable
|
|
because they require loading all data into the memory. To keep the memory requirement low, Yii
|
|
provides the so-called batch query support. A batch query makes uses of data cursor and fetches
|
|
data in batches.
|
|
|
|
Batch query can be used like the following:
|
|
|
|
```php
|
|
use yii\db\Query;
|
|
|
|
$query = (new Query())
|
|
->from('user')
|
|
->orderBy('id');
|
|
|
|
foreach ($query->batch() as $users) {
|
|
// $users is an array of 100 or fewer rows from the user table
|
|
}
|
|
|
|
// or if you want to iterate the row one by one
|
|
foreach ($query->each() as $user) {
|
|
// $user represents one row of data from the user table
|
|
}
|
|
```
|
|
|
|
The method [[yii\db\Query::batch()]] and [[yii\db\Query::each()]] return an [[yii\db\BatchQueryResult]] object
|
|
which implements the `Iterator` interface and thus can be used in the `foreach` construct.
|
|
During the first iteration, a SQL query is made to the database. Data are since then fetched in batches
|
|
in the iterations. By default, the batch size is 100, meaning 100 rows of data are being fetched in each batch.
|
|
You can change the batch size by passing the first parameter to the `batch()` or `each()` method.
|
|
|
|
Compared to the [[yii\db\Query::all()]], the batch query only loads 100 rows of data at a time into the memory.
|
|
If you process the data and then discard it right away, the batch query can help keep the memory usage under a limit.
|
|
|
|
If you specify the query result to be indexed by some column via [[yii\db\Query::indexBy()]], the batch query
|
|
will still keep the proper index. For example,
|
|
|
|
```php
|
|
use yii\db\Query;
|
|
|
|
$query = (new Query())
|
|
->from('user')
|
|
->indexBy('username');
|
|
|
|
foreach ($query->batch() as $users) {
|
|
// $users is indexed by the "username" column
|
|
}
|
|
|
|
foreach ($query->each() as $username => $user) {
|
|
}
|
|
```
|