Skip to content

Commit bbf8292

Browse files
committed
added method withAvg
1 parent 9c21f16 commit bbf8292

File tree

5 files changed

+225
-7
lines changed

5 files changed

+225
-7
lines changed

README.md

+9-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
## Why is this method needed?
88

9-
Now, when you trying to summarize a column in a related model, you get 2 queries in the database. With this method, it all turns into 1 query to the database and there is no need to load extra data.
9+
Now, if you want the sum or find the maximum column value in the related model, you will have two database queries. With this methods, it all turns into 1 query to the database and there is no need to load extra data.
1010
I often use this in my work and I hope it will be useful to you!
1111

1212
## Installation
@@ -29,16 +29,18 @@ class Invoice extends Model
2929

3030
If you want to get results from a relationship without actually loading them and by one request to the database you may use the these methods, which will place a new columns on your resulting models. For example:
3131
```php
32-
$invoices = Invoice::withSum('items:price,price2')->get();
33-
echo $invoices[0]->items_price_sum;
32+
$invoices = Invoice::withSum('items:price')
33+
->withMin('items:price')
34+
->withMax('items:price')
35+
->withAvg('items:price')
36+
->get();
3437

35-
$invoices = Invoice::withMin('items:price,price2')->get();
38+
echo $invoices[0]->items_price_sum;
3639
echo $invoices[0]->items_price_min;
37-
38-
$invoices = Invoice::withMax('items:price,price2')->get();
3940
echo $invoices[0]->items_price_max;
41+
echo $invoices[0]->items_price_avg;
4042
```
41-
The following methods apply to all methods!!!!
43+
### The following methods apply to all methods!!!
4244

4345
You may add the "sum" for multiple relations as well as add constraints to the queries:
4446
```php

src/Collection/LaravelSubQueryCollection.php

+32
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,36 @@ public function loadMax($relations)
102102

103103
return $this;
104104
}
105+
106+
/**
107+
* Load a set of relationship avg of column onto the collection.
108+
*
109+
* @param array|string $relations
110+
* @return $this
111+
*/
112+
public function loadAvg($relations)
113+
{
114+
if ($this->isEmpty()) {
115+
return $this;
116+
}
117+
118+
$models = $this->first()->newModelQuery()
119+
->whereKey($this->modelKeys())
120+
->select($this->first()->getKeyName())
121+
->withAvg(...func_get_args())
122+
->get();
123+
124+
$attributes = Arr::except(
125+
array_keys($models->first()->getAttributes()),
126+
$models->first()->getKeyName()
127+
);
128+
129+
$models->each(function ($model) use ($attributes) {
130+
$this->find($model->getKey())->forceFill(
131+
Arr::only($model->getAttributes(), $attributes)
132+
)->syncOriginalAttributes($attributes);
133+
});
134+
135+
return $this;
136+
}
105137
}

src/LaravelSubQuery.php

+17
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ class LaravelSubQuery extends Builder
3131
*/
3232
protected $withMax = [];
3333

34+
/**
35+
* The relationship avg value that should be eager loaded on every query.
36+
*
37+
* @var array
38+
*/
39+
protected $withAvg = [];
40+
3441
public function withSum($relations)
3542
{
3643
return $this->withSubQuery($relations, 'sum');
@@ -46,6 +53,11 @@ public function withMax($relations)
4653
return $this->withSubQuery($relations, 'max');
4754
}
4855

56+
public function withAvg($relations)
57+
{
58+
return $this->withSubQuery($relations, 'avg');
59+
}
60+
4961
protected function withSubQuery($relations, $type)
5062
{
5163
if (empty($relations)) {
@@ -140,4 +152,9 @@ public function setWithMax($withMax)
140152
{
141153
return $this->withMax($withMax);
142154
}
155+
156+
public function setWithAvg($withAvg)
157+
{
158+
return $this->withAvg($withAvg);
159+
}
143160
}

src/Traits/LaravelSubQueryTrait.php

+19
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,21 @@ public function loadMax($relations)
5454
return $this;
5555
}
5656

57+
/**
58+
* Eager load relation max value on the model.
59+
*
60+
* @param array|string $relations
61+
* @return $this
62+
*/
63+
public function loadAvg($relations)
64+
{
65+
$relations = is_string($relations) ? func_get_args() : $relations;
66+
67+
$this->newCollection([$this])->loadAvg($relations);
68+
69+
return $this;
70+
}
71+
5772
public function newEloquentBuilder($builder)
5873
{
5974
$newEloquentBuilder = new LaravelSubQuery($builder);
@@ -71,6 +86,10 @@ public function newEloquentBuilder($builder)
7186
$newEloquentBuilder->setWithMax($this->withMax);
7287
}
7388

89+
if (isset($this->withAvg)) {
90+
$newEloquentBuilder->setWithAvg($this->withAvg);
91+
}
92+
7493
return $newEloquentBuilder;
7594
}
7695

tests/LaravelSubQueryWithAvgTest.php

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
namespace Alexmg86\LaravelSubQuery\Tests;
4+
5+
use Alexmg86\LaravelSubQuery\Facades\LaravelSubQuery;
6+
use Alexmg86\LaravelSubQuery\ServiceProvider;
7+
use Illuminate\Database\Eloquent\Builder;
8+
use Illuminate\Database\Schema\Blueprint;
9+
use Illuminate\Support\Facades\Schema;
10+
11+
class LaravelSubQueryWithAvgTest extends DatabaseTestCase
12+
{
13+
protected function getPackageProviders($app)
14+
{
15+
return [ServiceProvider::class];
16+
}
17+
18+
protected function getPackageAliases($app)
19+
{
20+
return [
21+
'laravel-sub-query' => LaravelSubQuery::class,
22+
];
23+
}
24+
25+
protected function setUp(): void
26+
{
27+
parent::setUp();
28+
29+
Schema::create('invoices', function (Blueprint $table) {
30+
$table->increments('id');
31+
$table->string('name', 100);
32+
});
33+
34+
Schema::create('items', function (Blueprint $table) {
35+
$table->increments('id');
36+
$table->integer('invoice_id');
37+
$table->integer('price');
38+
$table->integer('price2');
39+
});
40+
41+
Schema::create('goods', function (Blueprint $table) {
42+
$table->increments('id');
43+
$table->integer('invoice_id');
44+
$table->integer('price');
45+
$table->integer('price2');
46+
});
47+
}
48+
49+
public function testBasic()
50+
{
51+
$invoice = Invoice::create(['id' => 1, 'name' => 'text_name']);
52+
for ($i = 1; $i < 11; $i++) {
53+
Item::create(['invoice_id' => $invoice->id, 'price' => $i, 'price2' => $i + 1]);
54+
Good::create(['invoice_id' => $invoice->id, 'price' => $i, 'price2' => $i + 1]);
55+
}
56+
57+
$results = Invoice::withAvg('items:price,price2');
58+
59+
$this->assertEquals([
60+
['id' => 1, 'name' => 'text_name', 'items_price_avg' => 5.5, 'items_price2_avg' => 6.5],
61+
], $results->get()->toArray());
62+
}
63+
64+
public function testWithConditions()
65+
{
66+
$invoice = Invoice::create(['id' => 1, 'name' => 'text_name']);
67+
for ($i = 1; $i < 11; $i++) {
68+
Item::create(['invoice_id' => $invoice->id, 'price' => $i, 'price2' => $i + 1]);
69+
Good::create(['invoice_id' => $invoice->id, 'price' => $i, 'price2' => $i + 1]);
70+
}
71+
72+
$results = Invoice::withAvg(['items:price', 'goods:price,price2' => function (Builder $query) {
73+
$query->where('price', '>', 6);
74+
}]);
75+
76+
$this->assertEquals([
77+
['id' => 1, 'name' => 'text_name', 'items_price_avg' => 5.5, 'goods_price_avg' => 8.5, 'goods_price2_avg' => 9.5],
78+
], $results->get()->toArray());
79+
}
80+
81+
public function testWithSelect()
82+
{
83+
$invoice = Invoice::create(['id' => 1, 'name' => 'text_name']);
84+
for ($i = 1; $i < 11; $i++) {
85+
Item::create(['invoice_id' => $invoice->id, 'price' => $i, 'price2' => $i + 1]);
86+
}
87+
88+
$results = Invoice::select(['id'])->withAvg('items:price');
89+
90+
$this->assertEquals([
91+
['id' => 1, 'items_price_avg' => 5.5],
92+
], $results->get()->toArray());
93+
}
94+
95+
public function testLoadAvg()
96+
{
97+
$invoice = Invoice::create(['id' => 1, 'name' => 'text_name']);
98+
for ($i = 1; $i < 11; $i++) {
99+
Item::create(['invoice_id' => $invoice->id, 'price' => $i, 'price2' => $i + 1]);
100+
}
101+
102+
$results = Invoice::first();
103+
$results->loadAvg('items:price');
104+
105+
$this->assertEquals(['id' => 1, 'name' => 'text_name', 'items_price_avg' => 5.5], $results->toArray());
106+
}
107+
108+
public function testLoadAvgWithConditions()
109+
{
110+
$invoice = Invoice::create(['id' => 1, 'name' => 'text_name']);
111+
for ($i = 1; $i < 11; $i++) {
112+
Item::create(['invoice_id' => $invoice->id, 'price' => $i, 'price2' => $i + 1]);
113+
}
114+
115+
$results = Invoice::first();
116+
$results->loadAvg(['items:price' => function ($query) {
117+
$query->where('price', '>', 5);
118+
}]);
119+
120+
$this->assertEquals(['id' => 1, 'name' => 'text_name', 'items_price_avg' => 8.0], $results->toArray());
121+
}
122+
123+
public function testGlobalScopes()
124+
{
125+
$invoice = Invoice::create(['id' => 1, 'name' => 'text_name']);
126+
for ($i = 1; $i < 11; $i++) {
127+
Good::create(['invoice_id' => $invoice->id, 'price' => $i, 'price2' => $i + 1]);
128+
}
129+
130+
$result = Invoice::withAvg('goods:price')->first();
131+
$this->assertEquals(8.0, $result->goods_price_avg);
132+
133+
$result = Invoice::withAvg('allGoods:price')->first();
134+
$this->assertEquals(5.5, $result->all_goods_price_avg);
135+
}
136+
137+
public function testSortingScopes()
138+
{
139+
$invoice = Invoice::create(['id' => 1, 'name' => 'text_name']);
140+
for ($i = 1; $i < 11; $i++) {
141+
Item::create(['invoice_id' => $invoice->id, 'price' => $i, 'price2' => $i + 1]);
142+
}
143+
144+
$result = Invoice::withAvg('items:price')->toSql();
145+
146+
$this->assertSame('select "invoices".*, (select avg(price) from "items" where "invoices"."id" = "items"."invoice_id") as "items_price_avg" from "invoices"', $result);
147+
}
148+
}

0 commit comments

Comments
 (0)