【问题标题】:Laravel: How to get SUM of a relation column using EloquentLaravel:如何使用 Eloquent 获取关系列的总和
【发布时间】:2020-02-28 04:34:15
【问题描述】:

如何在不加载整个关系数据的情况下使用预加载获取相关模型的 SUM?

在我的项目中有两个模型,AccountTransaction。账户模型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;
}

【问题讨论】:

    标签: php laravel laravel-5.8


    【解决方案1】:

    你需要子查询来做到这一点。我会告诉你一些解决方案:

    • 解决方案 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

      为 EloquentBuilder 创建一个 withSum 宏。

      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();
      

    【讨论】:

    • 不错。如果它正在回答您的问题,请接受答案?
    【解决方案2】:

    如果Account hasMany Transactions,您可以使用以下查询来获取金额

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

    您需要确保在 Closure 中选择了 account_id,否则关系将不起作用。

    或者,您还可以在 Account Model 中定义另一个关系,例如 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;
    }
    

    【讨论】:

    • @nmfzone 我不确定你所说的混乱。我已经展示了两种实现结果的方法,代码很简单。
    • @ascsoftw 谢谢你的回答。我已经测试了这两种解决方案。两者都有效。请注意,您在sum(account) 中有错字,应该是sum(amount)。得到结果$account-&gt;transactionSum[0]-&gt;sumAmount
    • @ab_ 很高兴为您提供帮助。抱歉打错了,我看错了列名。
    • @ascsoftw 您正在展示如何解决,但没有展示如何获得结果。向我们展示如何获得每次迭代的金额总和..
    • @nmfzone 总和存在于对象中。从对象中获取值并不是什么大问题。真正的问题是使用急切加载获得总和。
    【解决方案3】:

    哎呀,我在脑海中完全用COUNT 替换了SUM虽然这不会直接解决您的问题,但whereCount() 的底层代码可能会提供一些见解。


    Counting Related Models

    如果您想在不实际加载关系的情况下计算关系结果的数量,可以使用withCount 方法,该方法将在结果模型上放置一个{relation}_count 列。

    $accounts = Account::withCount('transactions')->get();
    
    foreach ($accounts as $account) {
        $transactionCount = $account->transactions_count;
    }
    

    【讨论】:

    • 感谢您的回复。我正在寻找withSum()。 ? 类似withCount().
    猜你喜欢
    • 2023-04-07
    • 1970-01-01
    • 2014-10-29
    • 2021-11-06
    • 1970-01-01
    • 2020-11-07
    • 1970-01-01
    • 2022-07-07
    • 2013-12-14
    相关资源
    最近更新 更多