Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] New method withSum for sum one or more columns for the relations #32474

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/Illuminate/Database/Eloquent/Builder.php
Original file line number Diff line number Diff line change
@@ -1128,6 +1128,34 @@ protected function parseWithRelations(array $relations)
return $results;
}

/**
* Parse a list of relations into individuals.
*
* @param array $relations
* @return array
*/
protected function parseWithSumRelations(array $relations)
{
$results = [];

foreach ($relations as $name => $constraints) {

// If the "name" value is a numeric key, we can assume that no constraints
// have been specified. We will just put an empty Closure there so that
// we can treat these all the same while we are looping through them.
if (is_numeric($name)) {
$name = $constraints;

[$name, $constraints] = [$name, static function () {
}];
}

$results[$name] = $constraints;
}

return $results;
}

/**
* Create a constraint to select the given columns for the relation.
*
32 changes: 32 additions & 0 deletions src/Illuminate/Database/Eloquent/Collection.php
Original file line number Diff line number Diff line change
@@ -95,6 +95,38 @@ public function loadCount($relations)
return $this;
}

/**
* Load a set of relationship sum of column onto the collection.
*
* @param array|string $relations
* @return $this
*/
public function loadSum($relations)
{
if ($this->isEmpty()) {
return $this;
}

$models = $this->first()->newModelQuery()
->whereKey($this->modelKeys())
->select($this->first()->getKeyName())
->withSum(...func_get_args())
->get();

$attributes = Arr::except(
array_keys($models->first()->getAttributes()),
$models->first()->getKeyName()
);

$models->each(function ($model) use ($attributes) {
$this->find($model->getKey())->forceFill(
Arr::only($model->getAttributes(), $attributes)
)->syncOriginalAttributes($attributes);
});

return $this;
}

/**
* Load a set of relationships onto the collection if they are not already eager loaded.
*
57 changes: 57 additions & 0 deletions src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php
Original file line number Diff line number Diff line change
@@ -411,6 +411,63 @@ public function withCount($relations)
return $this;
}

/**
* Add subselect queries to sum of columns in the relations.
*
* @param mixed $relations
* @return $this
*/
public function withSum($relations)
{
if (empty($relations)) {
return $this;
}

if (is_null($this->query->columns)) {
$this->query->select([$this->query->from.'.*']);
}

$relations = is_array($relations) ? $relations : func_get_args();

foreach ($this->parseWithSumRelations($relations) as $name => $constraints) {
$nameExplode = explode(':', $name);
$name = $nameExplode[0];
$columns = isset($nameExplode[1]) ? explode(',', $nameExplode[1]) : [];

$relation = $this->getRelationWithoutConstraints($name);

// Here we will get the relationship sum query and prepare to add it to the main query
// as a sub-select. First, we'll get the "has" query and use that to get the relation
// sum query. We will normalize the relation name then append _{column}_sum as the name.
foreach ($columns as $column) {
$query = $relation->getRelationExistenceSumQuery(
$relation->getRelated()->newQuery(), $this, $column
);

$query->callScope($constraints);

$query = $query->mergeConstraintsFrom($relation->getQuery())->toBase();

$query->orders = null;

$query->setBindings([], 'order');

if (count($query->columns) > 1) {
$query->columns = [$query->columns[0]];

$query->bindings['select'] = [];
}

// Finally we will add the proper result column alias to the query and run the subselect
// statement against the query builder. Then we will return the builder instance back
// to the developer for further constraint chaining that needs to take place on it.
$this->selectSub($query, Str::snake($name.'_'.$column.'_sum'));
}
}

return $this;
}

/**
* Add the "has" condition where clause to the query.
*
25 changes: 24 additions & 1 deletion src/Illuminate/Database/Eloquent/Model.php
Original file line number Diff line number Diff line change
@@ -77,6 +77,13 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*/
protected $withCount = [];

/**
* The relationship sums that should be eager loaded on every query.
*
* @var array
*/
protected $withSum = [];

/**
* The number of models to return for pagination.
*
@@ -547,6 +554,21 @@ public function loadCount($relations)
return $this;
}

/**
* Eager load relation sums on the model.
*
* @param array|string $relations
* @return $this
*/
public function loadSum($relations)
{
$relations = is_string($relations) ? func_get_args() : $relations;

$this->newCollection([$this])->loadSum($relations);

return $this;
}

/**
* Increment a column's value by a given amount.
*
@@ -1031,7 +1053,8 @@ public function newQueryWithoutScopes()
{
return $this->newModelQuery()
->with($this->with)
->withCount($this->withCount);
->withCount($this->withCount)
->withSum($this->withSum);
}

/**
14 changes: 14 additions & 0 deletions src/Illuminate/Database/Eloquent/Relations/Relation.php
Original file line number Diff line number Diff line change
@@ -196,6 +196,20 @@ public function getRelationExistenceCountQuery(Builder $query, Builder $parentQu
)->setBindings([], 'select');
}

/**
* Add the constraints for a relationship sum of column query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationExistenceSumQuery(Builder $query, Builder $parentQuery, $column)
{
return $this->getRelationExistenceQuery(
$query, $parentQuery, new Expression('sum('.$column.')')
)->setBindings([], 'select');
}

/**
* Add the constraints for an internal relationship existence query.
*