Yii2 framework backup
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

470 lines
17 KiB

クエリビルダとクエリ
====================
> 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` パラメータは必須であり、カラム名または式を取ります。
クエリを構築する
----------------
In the following, we will explain how to build various clauses in a SQL statement. For simplicity,
we use `$query` to represent a [[yii\db\Query]] object.
### `SELECT`
In order to form a basic `SELECT` query, you need to specify what columns to select and from what table:
```php
$query->select('id, name')
->from('user');
```
Select options can be specified as a comma-separated string, as in the above, or as an array.
The array syntax is especially useful when forming the selection dynamically:
```php
$query->select(['id', 'name'])
->from('user');
```
> Info: You should always use the array format if your `SELECT` clause contains SQL expressions.
> This is because a SQL expression like `CONCAT(first_name, last_name) AS full_name` may contain commas.
> If you list it together with other columns in a string, the expression may be split into several parts
> by commas, which is not what you want to see.
When specifying columns, you may include the table prefixes or column aliases, e.g., `user.id`, `user.id AS user_id`.
If you are using array to specify the columns, you may also use the array keys to specify the column aliases,
e.g., `['user_id' => 'user.id', 'user_name' => 'user.name']`.
Starting from version 2.0.1, you may also select sub-queries as columns. For example,
```php
$subQuery = (new Query)->select('COUNT(*)')->from('user');
$query = (new Query)->select(['id', 'count' => $subQuery])->from('post');
// $query represents the following SQL:
// SELECT `id`, (SELECT COUNT(*) FROM `user`) AS `count` FROM `post`
```
To select distinct rows, you may call `distinct()`, like the following:
```php
$query->select('user_id')->distinct()->from('post');
```
### `FROM`
To specify which table(s) to select data from, call `from()`:
```php
$query->select('*')->from('user');
```
You may specify multiple tables using a comma-separated string or an array.
Table names can contain schema prefixes (e.g. `'public.user'`) and/or table aliases (e.g. `'user u'`).
The method will automatically quote the table names unless it contains some parenthesis
(which means the table is given as a sub-query or DB expression). For example,
```php
$query->select('u.*, p.*')->from(['user u', 'post p']);
```
When the tables are specified as an array, you may also use the array keys as the table aliases
(if a table does not need alias, do not use a string key). For example,
```php
$query->select('u.*, p.*')->from(['u' => 'user', 'p' => 'post']);
```
You may specify a sub-query using a `Query` object. In this case, the corresponding array key will be used
as the alias for the sub-query.
```php
$subQuery = (new Query())->select('id')->from('user')->where('status=1');
$query->select('*')->from(['u' => $subQuery]);
```
### `WHERE`
Usually data is selected based upon certain criteria. Query Builder has some useful methods to specify these, the most powerful of which being `where`. It can be used in multiple ways.
The simplest way to apply a condition is to use a string:
```php
$query->where('status=:status', [':status' => $status]);
```
When using strings, make sure you're binding the query parameters, not creating a query by string concatenation. The above approach is safe to use, the following is not:
```php
$query->where("status=$status"); // Dangerous!
```
Instead of binding the status value immediately, you can do so using `params` or `addParams`:
```php
$query->where('status=:status');
$query->addParams([':status' => $status]);
```
Multiple conditions can simultaneously be set in `where` using the *hash format*:
```php
$query->where([
'status' => 10,
'type' => 2,
'id' => [4, 8, 15, 16, 23, 42],
]);
```
That code will generate the following SQL:
```sql
WHERE (`status` = 10) AND (`type` = 2) AND (`id` IN (4, 8, 15, 16, 23, 42))
```
NULL is a special value in databases, and is handled smartly by the Query Builder. This code:
```php
$query->where(['status' => null]);
```
results in this WHERE clause:
```sql
WHERE (`status` IS NULL)
```
You can also create sub-queries with `Query` objects like the following,
```php
$userQuery = (new Query)->select('id')->from('user');
$query->where(['id' => $userQuery]);
```
which will generate the following SQL:
```sql
WHERE `id` IN (SELECT `id` FROM `user`)
```
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) {
}
```