mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-11-04 14:46:19 +08:00 
			
		
		
		
	Added ActiveQuery::innerJoinWith().
This commit is contained in:
		@ -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.
 | 
			
		||||
In the above, the method [[ActiveQuery::innerJoinWith()|innerJoinWith()]] is a shortcut to [[ActiveQuery::joinWith()|joinWith()]]
 | 
			
		||||
with the join type set as `INNER JOIN`.
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
// 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();
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
@ -250,6 +250,20 @@ class ActiveQuery extends Query implements ActiveQueryInterface
 | 
			
		||||
		return $this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 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
 | 
			
		||||
 | 
			
		||||
@ -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');
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user