Browse Source

Added ActiveQuery::innerJoinWith().

tags/2.0.0-beta
Qiang Xue 11 years ago
parent
commit
0345191245
  1. 61
      docs/guide/active-record.md
  2. 2
      framework/CHANGELOG.md
  3. 24
      framework/yii/db/ActiveQuery.php
  4. 18
      tests/unit/framework/db/ActiveRecordTest.php

61
docs/guide/active-record.md

@ -391,35 +391,26 @@ Joining with Relations
When working with relational databases, a common task is to join multiple tables and apply various
query conditions and parameters to the JOIN SQL statement. Instead of calling [[ActiveQuery::join()]]
explicitly to build up the JOIN query, you may reuse the existing relation definitions and call [[ActiveQuery::joinWith()]]
to achieve the same goal. For example,
explicitly to build up the JOIN query, you may reuse the existing relation definitions and call
[[ActiveQuery::joinWith()]] to achieve this goal. For example,
```php
// find all orders and sort the orders by the customer id and the order id. also eager loading "customer"
$orders = Order::find()->joinWith('customer')->orderBy('tbl_customer.id, tbl_order.id')->all();
// find all orders that contain books, and eager loading "books"
$orders = Order::find()->joinWith('books')->all();
// find all orders that contain books, and sort the orders by the book names.
$orders = Order::find()->joinWith([
'books' => function ($query) {
$query->orderBy('tbl_item.id');
}
])->all();
$orders = Order::find()->innerJoinWith('books')->all();
```
Note that [[ActiveQuery::joinWith()]] differs from [[ActiveQuery::with()]] in that the former will build up
and execute a JOIN SQL statement for the primary model class. For example, `Order::find()->joinWith('books')->all()`
returns all orders that contain books, while `Order::find()->with('books')->all()` returns all orders
regardless they contain books or not.
Because `joinWith()` will cause generating a JOIN SQL statement, you are responsible to disambiguate column
names. For example, we use `tbl_item.id` to disambiguate the `id` column reference because both of the order table
and the item table contain a column named `id`.
In the above, the method [[ActiveQuery::innerJoinWith()|innerJoinWith()]] is a shortcut to [[ActiveQuery::joinWith()|joinWith()]]
with the join type set as `INNER JOIN`.
You may join with one or multiple relations. You may also join with sub-relations. For example,
You may join with one or multiple relations; you may apply query conditions to the relations on-the-fly;
and you may also join with sub-relations. For example,
```php
// join with multiple relations
// find out the orders that contain books and are placed by customers who registered within the past 24 hours
$orders = Order::find()->joinWith([
$orders = Order::find()->innerJoinWith([
'books',
'customer' => function ($query) {
$query->where('tbl_customer.create_time > ' . (time() - 24 * 3600));
@ -429,23 +420,37 @@ $orders = Order::find()->joinWith([
$orders = Order::find()->joinWith('books.author')->all();
```
Behind the scene, Yii will first execute a JOIN SQL statement to bring back the primary models
satisfying the conditions applied to the JOIN SQL. It will then execute a query for each relation
and populate the corresponding related records.
The difference between [[ActiveQuery::joinWith()|joinWith()]] and [[ActiveQuery::with()|with()]] is that
the former joins the tables for the primary model class and the related model classes to retrieve
the primary models, while the latter just queries against the table for the primary model class to
retrieve the primary models.
Because of this difference, you may apply query conditions that are only available to a JOIN SQL statement.
For example, you may filter the primary models by the conditions on the related models, like the example
above. You may also sort the primary models using columns from the related tables.
When using [[ActiveQuery::joinWith()|joinWith()]], you are responsible to disambiguate column names.
In the above examples, we use `tbl_item.id` and `tbl_order.id` to disambiguate the `id` column references
because both of the order table and the item table contain a column named `id`.
By default, when you join with a relation, the relation will also be eagerly loaded. You may change this behavior
by passing the `$eagerLoading` parameter which specifies whether to eager load the specified relations.
Also, when the relations are joined with the primary table, the default join type is `INNER JOIN`. You may change
to use other type of joins, such as `LEFT JOIN`.
And also by default, [[ActiveQuery::joinWith()|joinWith()]] uses `LEFT JOIN` to join the related tables.
You may pass it with the `$joinType` parameter to customize the join type. As a shortcut to the `INNER JOIN` type,
you may use [[ActiveQuery::innerJoinWith()|innerJoinWith()]].
Below are some more examples,
```php
// find all orders that contain books, but do not eager loading "books".
$orders = Order::find()->joinWith('books', false)->all();
// find all orders and sort them by the customer IDs. Do not eager loading "customer".
$orders = Order::find()->joinWith([
'customer' => function ($query) {
$query->orderBy('tbl_customer.id');
},
], false, 'LEFT JOIN')->all();
$orders = Order::find()->innerJoinWith('books', false)->all();
// equivalent to the above
$orders = Order::find()->joinWith('books', false, 'INNER JOIN')->all();
```

2
framework/CHANGELOG.md

@ -27,7 +27,7 @@ Yii Framework 2 Change Log
- Enh #1552: It is now possible to use multiple bootstrap NavBar in a single page (Alex-Code)
- Enh #1572: Added `yii\web\Controller::createAbsoluteUrl()` (samdark)
- Enh #1579: throw exception when the given AR relation name does not match in a case sensitive manner (qiangxue)
- Enh #1581: Added `ActiveQuery::joinWith()` to support joining with relations (qiangxue)
- Enh #1581: Added `ActiveQuery::joinWith()` and `ActiveQuery::innerJoinWith()` to support joining with relations (qiangxue)
- Enh #1601: Added support for tagName and encodeLabel parameters in ButtonDropdown (omnilight)
- Enh: Added `favicon.ico` and `robots.txt` to defauly application templates (samdark)
- Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue)

24
framework/yii/db/ActiveQuery.php

@ -200,8 +200,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
*
* Note that because a JOIN query will be performed, you are responsible to disambiguate column names.
*
* This method differs from [[with()]] in that it will build up and execute a JOIN SQL statement.
* When `$eagerLoading` is true, it will call [[with()]] in addition with the specified relations.
* This method differs from [[with()]] in that it will build up and execute a JOIN SQL statement
* for the primary table. And when `$eagerLoading` is true, it will call [[with()]] in addition with the specified relations.
*
* @param array $with the relations to be joined. Each array element represents a single relation.
* The array keys are relation names, and the array values are the corresponding anonymous functions that
@ -211,8 +211,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
*
* ```php
* // find all orders that contain books, and eager loading "books"
* Order::find()->joinWith('books')->all();
* // find all orders that contain books, and sort the orders by the book names.
* Order::find()->joinWith('books', true, 'INNER JOIN')->all();
* // find all orders, eager loading "books", and sort the orders and books by the book names.
* Order::find()->joinWith([
* 'books' => function ($query) {
* $query->orderBy('tbl_item.name');
@ -228,7 +228,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
* in the format of `relationName => joinType` to specify different join types for different relations.
* @return static the query object itself
*/
public function joinWith($with, $eagerLoading = true, $joinType = 'INNER JOIN')
public function joinWith($with, $eagerLoading = true, $joinType = 'LEFT JOIN')
{
$with = (array)$with;
$this->joinWithRelations(new $this->modelClass, $with, $joinType);
@ -251,6 +251,20 @@ class ActiveQuery extends Query implements ActiveQueryInterface
}
/**
* Inner joins with the specified relations.
* This is a shortcut method to [[joinWith()]] with the join type set as "INNER JOIN".
* Please refer to [[joinWith()]] for detailed usage of this method.
* @param array $with the relations to be joined with
* @param boolean|array $eagerLoading whether to eager loading the relations
* @return static the query object itself
* @see joinWith()
*/
public function innerJoinWith($with, $eagerLoading = true)
{
return $this->joinWith($with, $eagerLoading, 'INNER JOIN');
}
/**
* Modifies the current query by adding join fragments based on the given relations.
* @param ActiveRecord $model the primary model
* @param array $with the relations to be joined

18
tests/unit/framework/db/ActiveRecordTest.php

@ -220,8 +220,18 @@ class ActiveRecordTest extends DatabaseTestCase
public function testJoinWith()
{
// left join and eager loading
$orders = Order::find()->joinWith('customer')->orderBy('tbl_customer.id DESC, tbl_order.id')->all();
$this->assertEquals(3, count($orders));
$this->assertEquals(2, $orders[0]->id);
$this->assertEquals(3, $orders[1]->id);
$this->assertEquals(1, $orders[2]->id);
$this->assertTrue($orders[0]->isRelationPopulated('customer'));
$this->assertTrue($orders[1]->isRelationPopulated('customer'));
$this->assertTrue($orders[2]->isRelationPopulated('customer'));
// inner join filtering and eager loading
$orders = Order::find()->joinWith([
$orders = Order::find()->innerJoinWith([
'customer' => function ($query) {
$query->where('tbl_customer.id=2');
},
@ -233,7 +243,7 @@ class ActiveRecordTest extends DatabaseTestCase
$this->assertTrue($orders[1]->isRelationPopulated('customer'));
// inner join filtering without eager loading
$orders = Order::find()->joinWith([
$orders = Order::find()->innerJoinWith([
'customer' => function ($query) {
$query->where('tbl_customer.id=2');
},
@ -245,7 +255,7 @@ class ActiveRecordTest extends DatabaseTestCase
$this->assertFalse($orders[1]->isRelationPopulated('customer'));
// join with via-relation
$orders = Order::find()->joinWith('books')->orderBy('tbl_order.id')->all();
$orders = Order::find()->innerJoinWith('books')->orderBy('tbl_order.id')->all();
$this->assertEquals(2, count($orders));
$this->assertEquals(1, $orders[0]->id);
$this->assertEquals(3, $orders[1]->id);
@ -255,7 +265,7 @@ class ActiveRecordTest extends DatabaseTestCase
$this->assertEquals(1, count($orders[1]->books));
// join with sub-relation
$orders = Order::find()->joinWith([
$orders = Order::find()->innerJoinWith([
'items.category' => function ($q) {
$q->where('tbl_category.id = 2');
},

Loading…
Cancel
Save