Commit 03451912 by Qiang Xue

Added ActiveQuery::innerJoinWith().

parent dc720d9b
...@@ -391,35 +391,26 @@ Joining with Relations ...@@ -391,35 +391,26 @@ Joining with Relations
When working with relational databases, a common task is to join multiple tables and apply various 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()]] 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()]] explicitly to build up the JOIN query, you may reuse the existing relation definitions and call
to achieve the same goal. For example, [[ActiveQuery::joinWith()]] to achieve this goal. For example,
```php ```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" // find all orders that contain books, and eager loading "books"
$orders = Order::find()->joinWith('books')->all(); $orders = Order::find()->innerJoinWith('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();
``` ```
Note that [[ActiveQuery::joinWith()]] differs from [[ActiveQuery::with()]] in that the former will build up In the above, the method [[ActiveQuery::innerJoinWith()|innerJoinWith()]] is a shortcut to [[ActiveQuery::joinWith()|joinWith()]]
and execute a JOIN SQL statement for the primary model class. For example, `Order::find()->joinWith('books')->all()` with the join type set as `INNER JOIN`.
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`.
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 ```php
// join with multiple relations // join with multiple relations
// find out the orders that contain books and are placed by customers who registered within the past 24 hours // 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', 'books',
'customer' => function ($query) { 'customer' => function ($query) {
$query->where('tbl_customer.create_time > ' . (time() - 24 * 3600)); $query->where('tbl_customer.create_time > ' . (time() - 24 * 3600));
...@@ -429,23 +420,37 @@ $orders = Order::find()->joinWith([ ...@@ -429,23 +420,37 @@ $orders = Order::find()->joinWith([
$orders = Order::find()->joinWith('books.author')->all(); $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 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. 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 And also by default, [[ActiveQuery::joinWith()|joinWith()]] uses `LEFT JOIN` to join the related tables.
to use other type of joins, such as `LEFT JOIN`. 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, Below are some more examples,
```php ```php
// find all orders that contain books, but do not eager loading "books". // find all orders that contain books, but do not eager loading "books".
$orders = Order::find()->joinWith('books', false)->all(); $orders = Order::find()->innerJoinWith('books', false)->all();
// find all orders and sort them by the customer IDs. Do not eager loading "customer". // equivalent to the above
$orders = Order::find()->joinWith([ $orders = Order::find()->joinWith('books', false, 'INNER JOIN')->all();
'customer' => function ($query) {
$query->orderBy('tbl_customer.id');
},
], false, 'LEFT JOIN')->all();
``` ```
......
...@@ -27,7 +27,7 @@ Yii Framework 2 Change Log ...@@ -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 #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 #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 #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 #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 `favicon.ico` and `robots.txt` to defauly application templates (samdark)
- Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue) - Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue)
......
...@@ -200,8 +200,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface ...@@ -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. * 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. * 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. * 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. * @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 * 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 ...@@ -211,8 +211,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
* *
* ```php * ```php
* // find all orders that contain books, and eager loading "books" * // find all orders that contain books, and eager loading "books"
* Order::find()->joinWith('books')->all(); * Order::find()->joinWith('books', true, 'INNER JOIN')->all();
* // find all orders that contain books, and sort the orders by the book names. * // find all orders, eager loading "books", and sort the orders and books by the book names.
* Order::find()->joinWith([ * Order::find()->joinWith([
* 'books' => function ($query) { * 'books' => function ($query) {
* $query->orderBy('tbl_item.name'); * $query->orderBy('tbl_item.name');
...@@ -228,7 +228,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface ...@@ -228,7 +228,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
* in the format of `relationName => joinType` to specify different join types for different relations. * in the format of `relationName => joinType` to specify different join types for different relations.
* @return static the query object itself * @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; $with = (array)$with;
$this->joinWithRelations(new $this->modelClass, $with, $joinType); $this->joinWithRelations(new $this->modelClass, $with, $joinType);
...@@ -251,6 +251,20 @@ class ActiveQuery extends Query implements ActiveQueryInterface ...@@ -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. * Modifies the current query by adding join fragments based on the given relations.
* @param ActiveRecord $model the primary model * @param ActiveRecord $model the primary model
* @param array $with the relations to be joined * @param array $with the relations to be joined
......
...@@ -220,8 +220,18 @@ class ActiveRecordTest extends DatabaseTestCase ...@@ -220,8 +220,18 @@ class ActiveRecordTest extends DatabaseTestCase
public function testJoinWith() 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 // inner join filtering and eager loading
$orders = Order::find()->joinWith([ $orders = Order::find()->innerJoinWith([
'customer' => function ($query) { 'customer' => function ($query) {
$query->where('tbl_customer.id=2'); $query->where('tbl_customer.id=2');
}, },
...@@ -233,7 +243,7 @@ class ActiveRecordTest extends DatabaseTestCase ...@@ -233,7 +243,7 @@ class ActiveRecordTest extends DatabaseTestCase
$this->assertTrue($orders[1]->isRelationPopulated('customer')); $this->assertTrue($orders[1]->isRelationPopulated('customer'));
// inner join filtering without eager loading // inner join filtering without eager loading
$orders = Order::find()->joinWith([ $orders = Order::find()->innerJoinWith([
'customer' => function ($query) { 'customer' => function ($query) {
$query->where('tbl_customer.id=2'); $query->where('tbl_customer.id=2');
}, },
...@@ -245,7 +255,7 @@ class ActiveRecordTest extends DatabaseTestCase ...@@ -245,7 +255,7 @@ class ActiveRecordTest extends DatabaseTestCase
$this->assertFalse($orders[1]->isRelationPopulated('customer')); $this->assertFalse($orders[1]->isRelationPopulated('customer'));
// join with via-relation // 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(2, count($orders));
$this->assertEquals(1, $orders[0]->id); $this->assertEquals(1, $orders[0]->id);
$this->assertEquals(3, $orders[1]->id); $this->assertEquals(3, $orders[1]->id);
...@@ -255,7 +265,7 @@ class ActiveRecordTest extends DatabaseTestCase ...@@ -255,7 +265,7 @@ class ActiveRecordTest extends DatabaseTestCase
$this->assertEquals(1, count($orders[1]->books)); $this->assertEquals(1, count($orders[1]->books));
// join with sub-relation // join with sub-relation
$orders = Order::find()->joinWith([ $orders = Order::find()->innerJoinWith([
'items.category' => function ($q) { 'items.category' => function ($q) {
$q->where('tbl_category.id = 2'); $q->where('tbl_category.id = 2');
}, },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment