query-builder.md 10.5 KB
Newer Older
Alexander Makarov committed
1 2 3
Query Builder and Query
=======================

4 5 6 7
Yii provides a basic database access layer as described in the [Database basics](database-basics.md) section.
The database access layer provides a low-level way to interact with the database. While useful in some situations,
it can be tedious and error-prone to write raw SQLs. An alternative approach is to use the Query Builder.
The Query Builder provides an object-oriented vehicle for generating queries to be executed.
Alexander Makarov committed
8

9
A typical usage of the query builder looks like the following:
Alexander Makarov committed
10 11

```php
12 13 14 15 16 17 18 19
$rows = (new \yii\db\Query)
	->select('id, name')
	->from('tbl_user')
	->limit(10)
	->createCommand()
	->queryAll();

// which is equivalent to the following code:
Alexander Makarov committed
20

21
$query = new \yii\db\Query;
Alexander Makarov committed
22
$query->select('id, name')
23 24
	->from('tbl_user')
	->limit(10);
Alexander Makarov committed
25

26
// Create a command. 
Alexander Makarov committed
27
$command = $query->createCommand();
28 29 30
// You can get the actual SQL using $command->sql

// Execute the command:
Alexander Makarov committed
31 32 33
$rows = $command->queryAll();
```

34 35 36 37 38 39
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`
--------
Alexander Makarov committed
40

41
In order to form a basic `SELECT` query, you need to specify what columns to select and from what table:
Alexander Makarov committed
42 43 44 45 46 47

```php
$query->select('id, name')
	->from('tbl_user');
```

48 49
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:
Alexander Makarov committed
50 51

```php
52
$query->select(['id', 'name'])
53
	->from('tbl_user');
Alexander Makarov committed
54 55
```

56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
> 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., `tbl_user.id`, `tbl_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' => 'tbl_user.id', 'user_name' => 'tbl_user.name']`.

To select distinct rows, you may call `distinct()`, like the following:

```php
$query->select('user_id')->distinct()->from('tbl_post');
```

`FROM`
------

To specify which table(s) to select data from, call `from()`:

```php
$query->select('*')->from('tbl_user');
```

You may specify multiple tables using a comma-separated string or an array.
Table names can contain schema prefixes (e.g. `'public.tbl_user'`) and/or table aliases (e.g. `'tbl_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(['tbl_user u', 'tbl_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' => 'tbl_user u', 'p' => 'tbl_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.
Qiang Xue committed
98

99 100 101 102
```php
$subQuery = (new Query)->select('id')->from('tbl_user')->where('status=1');
$query->select('*')->from(['u' => $subQuery]);
```
Qiang Xue committed
103

104 105

`JOIN`
106
-----
Alexander Makarov committed
107

108
The `JOIN` clauses are generated in the Query Builder by using the applicable join method:
Alexander Makarov committed
109

110 111 112
- `innerJoin()`
- `leftJoin()`
- `rightJoin()`
Alexander Makarov committed
113

114 115 116
This left join selects data from two related tables in one query:

```php
117 118
$query->select(['tbl_user.name AS author', 'tbl_post.title as title'])
	->from('tbl_user')
119 120 121
	->leftJoin('tbl_post', 'tbl_post.user_id = tbl_user.id'); 
```

122 123
In the code, the `leftJoin()` method's first parameter
specifies the table to join to. The second parameter defines the join condition.
124 125

If your database application supports other join types, you can use those via the  generic `join` method:
Alexander Makarov committed
126 127 128 129 130

```php
$query->join('FULL OUTER JOIN', 'tbl_post', 'tbl_post.user_id = tbl_user.id');
```

131 132
The first argument is the join type to perform. The second is the table to join to, and the third is the condition.

133 134 135 136 137 138 139 140 141 142
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');
```

`WHERE`
-------
Alexander Makarov committed
143

144
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.
Alexander Makarov committed
145

146
The simplest way to apply a condition is to use a string:
Alexander Makarov committed
147 148

```php
Alexander Makarov committed
149
$query->where('status=:status', [':status' => $status]);
Alexander Makarov committed
150 151
```

152
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:
Alexander Makarov committed
153

154 155 156 157 158
```php
$query->where("status=$status"); // Dangerous!
```

Instead of binding the status value immediately, you can do so using `params` or `addParams`:
Alexander Makarov committed
159 160 161

```php
$query->where('status=:status');
Alexander Makarov committed
162
$query->addParams([':status' => $status]);
Alexander Makarov committed
163 164
```

165
Multiple conditions can simultaneously be set in `where` using the *hash format*:
Alexander Makarov committed
166 167

```php
Alexander Makarov committed
168
$query->where([
Alexander Makarov committed
169 170
	'status' => 10,
	'type' => 2,
Alexander Makarov committed
171 172
	'id' => [4, 8, 15, 16, 23, 42],
]);
Alexander Makarov committed
173 174
```

175
That code will generate the following SQL:
Alexander Makarov committed
176 177 178 179 180

```sql
WHERE (`status` = 10) AND (`type` = 2) AND (`id` IN (4, 8, 15, 16, 23, 42))
```

181
NULL is a special value in databases, and is handled smartly by the Query Builder. This code:
Alexander Makarov committed
182 183

```php
Alexander Makarov committed
184
$query->where(['status' => null]);
Alexander Makarov committed
185 186
```

187
results in this WHERE clause:
Alexander Makarov committed
188 189 190 191 192

```sql
WHERE (`status` IS NULL)
```

Alexander Makarov committed
193
Another way to use the method is the operand format which is `[operator, operand1, operand2, ...]`.
Alexander Makarov committed
194 195 196 197

Operator can be one of the following:

- `and`: the operands should be concatenated together using `AND`. For example,
Alexander Makarov committed
198
  `['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array,
Alexander Makarov committed
199
  it will be converted into a string using the rules described here. For example,
Alexander Makarov committed
200
  `['and', 'type=1', ['or', 'id=1', 'id=2']]` will generate `type=1 AND (id=1 OR id=2)`.
Alexander Makarov committed
201 202 203 204
  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.
Alexander Makarov committed
205
   For example, `['between', 'id', 1, 10]` will generate `id BETWEEN 1 AND 10`.
Alexander Makarov committed
206 207 208 209
- `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, and operand 2 be an array representing
  the range of the values that the column or DB expression should be in. For example,
Alexander Makarov committed
210
  `['in', 'id', [1, 2, 3]]` will generate `id IN (1, 2, 3)`.
Alexander Makarov committed
211 212 213 214
  The method will properly quote the column name and escape values in the range.
- `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.
215
  For example, `['like', 'name', 'tester']` will generate `name LIKE '%tester%'`.
Alexander Makarov committed
216
  When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
217
  using `AND`. For example, `['like', 'name', ['test', 'sample']]` will generate
Alexander Makarov committed
218
  `name LIKE '%test%' AND name LIKE '%sample%'`.
219 220 221 222 223 224
  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.
Alexander Makarov committed
225 226 227 228 229 230
- `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.
231
- `exists`: requires one operand which must be an instance of [[yii\db\Query]] representing the sub-query.
232 233
  It will build a `EXISTS (sub-query)` expression.
- `not exists`: similar to the `exists` operator and builds a `NOT EXISTS (sub-query)` expression.
Alexander Makarov committed
234 235 236 237 238 239 240

If you are building parts of condition dynamically it's very convenient to use `andWhere` and `orWhere`:

```php
$status = 10;
$search = 'yii';

Alexander Makarov committed
241
$query->where(['status' => $status]);
Alexander Makarov committed
242
if (!empty($search)) {
Carsten Brandt committed
243
	$query->andWhere(['like', 'title', $search]);
Alexander Makarov committed
244 245 246 247 248 249 250 251 252
}
```

In case `$search` isn't empty the following SQL will be generated:

```sql
WHERE (`status` = 10) AND (`title` LIKE '%yii%')
```

253
`ORDER BY`
Alexander Makarov committed
254 255
-----

256
For ordering results `orderBy` and `addOrderBy` could be used:
Alexander Makarov committed
257 258

```php
Alexander Makarov committed
259
$query->orderBy([
260 261
	'id' => SORT_ASC,
	'name' => SORT_DESC,
Alexander Makarov committed
262
]);
Alexander Makarov committed
263 264 265 266
```

Here we are ordering by `id` ascending and then by `name` descending.

267 268
```

Alexander Makarov committed
269 270 271 272 273 274 275 276 277 278 279 280
Group 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
Alexander Makarov committed
281
$query->addGroupBy(['created_at', 'updated_at']);
Alexander Makarov committed
282 283 284 285 286 287
```

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
Alexander Makarov committed
288
$query->having(['status' => $status]);
Alexander Makarov committed
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
```

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);
```

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, 'post' as type, name")->from('tbl_post')->limit(10);

$anotherQuery = new Query;
Alexander Makarov committed
317
$anotherQuery->select('id, 'user' as type, name')->from('tbl_user')->limit(10);
Alexander Makarov committed
318 319 320 321

$query->union($anotherQuery);
```