Laravel: Как получить СУММ столбца отношений с активной загрузкой - PullRequest
1 голос
/ 02 ноября 2019

Как получить СУММ на связанной модели, используя готовую загрузку, не загружая целые данные отношения?

В моем проекте есть две модели, Account и Transaction. Модель аккаунта has many транзакций.

Мое требование: : Получать аккаунты и стремиться загрузить только сумму на соответствующую таблицу .

Мой текущий код предоставлен : В этом коде transactions загружены с нетерпением и сумма рассчитывается с помощью php. Но я бы предпочел не загружать целые транзакции. Единственное требование: sum('amount').

таблица: счета

| id | name | address | ...

таблица: транзакции

| id | account_id | amount | ...

Account.php

/**
 * Get the transaction records associated with the account.
 */
public function transactions()
{
    return $this->hasMany('App\Models\Transaction', 'account_id');
}

Следующий код дает каждой учетной записи и ее транзакции.

$account = Account::with(['transactions'])->get();

SUMрассчитывается по формуле:

foreach ($accounts as $key => $value) {
    echo $value->transactions->sum('amount'). " <br />";
}

Я пробовал что-то подобное, но не сработало.

public function transactions()
{
    return $this->hasMany('App\Models\Transaction', 'account_id')->sum('amount;
}

Ответы [ 3 ]

1 голос
/ 02 ноября 2019

Вам нужен подзапрос, чтобы сделать это. Я покажу вам какое-нибудь решение:

  • Решение 1

    $amountSum = Transaction::selectRaw('sum(amount)')
        ->whereColumn('account_id', 'accounts.id')
        ->getQuery();
    
    $accounts = Account::select('accounts.*')
        ->selectSub($amountSum, 'amount_sum')
        ->get();
    
    foreach($accounts as $account) {
        echo $account->amount_sum;
    }
    
  • Решение 2

    Создание withSumмакрос в EloquentBuilder.

    use Illuminate\Support\Str;
    use Illuminate\Database\Eloquent\Builder;
    use Illuminate\Database\Query\Expression;
    
    Builder::macro('withSum', function ($columns) {
        if (empty($columns)) {
            return $this;
        }
    
        if (is_null($this->query->columns)) {
            $this->query->select([$this->query->from.'.*']);
        }
    
        $columns = is_array($columns) ? $columns : func_get_args();
        $columnAndConstraints = [];
    
        foreach ($columns as $name => $constraints) {
            // If the "name" value is a numeric key, we can assume that no
            // constraints have been specified. We'll just put an empty
            // Closure there, so that we can treat them all the same.
            if (is_numeric($name)) {
                $name = $constraints;
                $constraints = static function () {
                    //
                };
            }
    
            $columnAndConstraints[$name] = $constraints;
        }
    
        foreach ($columnAndConstraints as $name => $constraints) {
            $segments = explode(' ', $name);
    
            unset($alias);
    
            if (count($segments) === 3 && Str::lower($segments[1]) === 'as') {
                [$name, $alias] = [$segments[0], $segments[2]];
            }
    
            // Here we'll extract the relation name and the actual column name that's need to sum.
            $segments = explode('.', $name);
    
            $relationName = $segments[0];
            $column = $segments[1];
    
            $relation = $this->getRelationWithoutConstraints($relationName);
    
            $query = $relation->getRelationExistenceQuery(
                $relation->getRelated()->newQuery(),
                $this,
                new Expression("sum(`$column`)")
            )->setBindings([], 'select');
    
            $query->callScope($constraints);
    
            $query = $query->mergeConstraintsFrom($relation->getQuery())->toBase();
    
            if (count($query->columns) > 1) {
                $query->columns = [$query->columns[0]];
            }
    
            // 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.
            $column = $alias ?? Str::snake(Str::replaceFirst('.', ' ', $name.'_sum'));
    
            $this->selectSub($query, $column);
        }
    
        return $this;
    });
    

    Затем вы можете использовать его так же, как при использовании withCount, за исключением того, что вам нужно добавить столбец, который необходимо суммировать после отношений (relation.column).

    $accounts = Account::withSum('transactions.amount')->get();
    
    foreach($accounts as $account) {
        // You can access the sum result using format `relation_column_sum`
        echo $account->transactions_amount_sum;
    }
    
    $accounts = Account::withSum(['transactions.amount' => function (Builder $query) {
        $query->where('status', 'APPROVED');
    })->get();
    
0 голосов
/ 02 ноября 2019

Если Account hasMany Transactions, вы можете использовать следующий запрос, чтобы получить сумму

Account::with(['transactions' =>  function( $q) {
    $q->selectRaw('sum(amount) as sum_amount, account_id')->groupBy('account_id');
}

. Необходимо убедиться, что в замыкании выбран account_id, иначе связь не будет работать.

В качестве альтернативы вы также можете определить другое отношение, например transactionSums, как показано ниже в модели счета:

public function transactionSums() {

    return $this->hasMany(Transaction::class)->selectRaw('sum(amount) as sum_amount, account_id')->groupBy('account_id');
}

Тогда код вашего контроллера будет чище, как показано ниже:

$accounts = Account::with(['transactionSums' ]);

foreach($accounts as $account)
{
    echo $account->transactionSums[0]->sum_amount;
}
0 голосов
/ 02 ноября 2019

Ой, я полностью заменил SUM на COUNT в своей голове. Хотя это не решит вашу проблему напрямую, базовый код для whereCount() может дать некоторое представление.


Подсчет связанных моделей

Если вы хотите подсчитать количество результатов отношения без фактической загрузки, вы можете использовать метод withCount, который будетпоместите столбец {relation}_count на получающиеся модели.

$accounts = Account::withCount('transactions')->get();

foreach ($accounts as $account) {
    $transactionCount = $account->transactions_count;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...