如何在 Laravel Eloquent 中添加相关的聚合数据?

Posted

技术标签:

【中文标题】如何在 Laravel Eloquent 中添加相关的聚合数据?【英文标题】:How to add related aggregate data in Laravel Eloquent? 【发布时间】:2019-12-08 14:36:06 【问题描述】:

一个度量模型有许多测量值。

我已经建立了关系,所以我可以做以下事情:

$metrics = Metric::with('measurements' => function($query) 
   return $query->where('timestamp', '>=', '20190731');
);

这可用于获取 2019 年 7 月 31 日以来的所有度量标准。

但我现在想要实现的是获取聚合的测量数据。

编辑:这是对我所追求的更好的解释

如果上面给出的是这样的:

results = [
            id: 1,
           name: 'Metric 1',
   measurements: [
         id: 123, metric_id: 1, value: 4,  timestamp: 2019-07-31T09:00 ,
         id: 124, metric_id: 1, value: 10, timestamp: 2019-07-31T10:00 ,
        ...
         id: 124, metric_id: 1, value: 10, timestamp: 2019-08-01T09:00 ,
                 ] ,
  ...
]

那么我想要达到的结果会是这样的:;

results = [
            id: 1,
           name: 'Metric 1',
   measurementStats: [
         metric_id: 1, period: 2019-07-31, min: 4, max: 10 ,
         metric_id: 1, period: 2019-08-01, min: 10, max: 10 ,
                 ] ,
  ...
]

我想当急切加载时,Eloquent 应该像这样运行:

  SELECT metric_id,
         DATE_FORMAT(timestamp, '%Y%m%d') as period,
         MIN(value) AS min,
         MAX(value) AS max
    FROM measurements
   WHERE metric_id IN (...)
GROUP BY metric_id, DATE_FORMAT(timestamp, '%Y%m%d')

有没有办法在 Eloquent/Laravel 查询构建器中实现这一点? (Laravel 5.8)

【问题讨论】:

能否稍微解释一下sql查询,这样要求就很容易理解了 @SagarGautam 感谢您抽出宝贵时间,我已经扩展了问题,是否更清楚? 是的,它可以做到,我做了类似的事情。您可以查看 Laravel 手册 laravel.com/docs/5.8/queries#joins 的这一页,您肯定可以自己获得它。你只需要把这些碎片放在一起。使用示例 DB: raw ("DATE_FORMAT(timestamp, '%Y%M%d') as period") 进行复杂查询。 @NewWorldNeverland 我知道如果我放弃 Eloquent 我可以做到,但我希望有类似function measurementStats() return $this->hasMany(...); 这样我可以使用急切加载? @artfulrobot 我已经添加了我的答案试试看 【参考方案1】:

我曾希望使用 Laravel 自己的渴望加载,但使用来自我的统计查询的派生“模型”,但我认为这是不可能的; Laravel 的急切加载过于依赖 Eloquent 模型。但是,我确实找到了这个解决方案:

度量模型的变化

我们会:

    创建一个 measureStats 属性(创建公共属性的一种非常冗长的方法!)

    告诉模型使用自定义集合而不是普通的 Eloquent 集合。

    告诉模型在将自身转换为 JSON 时附加 stats 属性。

App/Metric.php

namespace App;

use App\MetricCollection;
...
class Metric

  ...

  protected $appends = ['measurementStats'];

  /** @var array */
  protected $measurementStats;

  public function getMeasurementStatsAttribute() 
    return $this->measurementStats ?? [];
  

  public function setMeasurementStatsAttribute($stats) 
    $this->measurementStats = $stats;
  

  public function newCollection(array $models=[]) 
    return new MetricCollection($models);
  

自定义集合

这将使我们能够在一个查询中加载所有测量统计数据并将它们附加到模型中。

namespace App;

  use \Illuminate\Database\Eloquent\Collection;
  use \DB;

  class MetricCollection extends Collection
  
    public function addMeasurementStats($filters) 
      $metric_ids = $this->modelKeys();
      $stats_query = DB::table('measurements')
        ->select('metric_id')
        ->selectRaw("DATE_FORMAT(timestamp, '%Y%m%d') as period")
        ->selectRaw("MIN(value) AS min")
        ->selectRaw("MAX(value) AS max")
        ->selectRaw("AVG(value) AS value")
        ->whereIn('metric_id', $metric_ids)
        ->groupBy('metric_id', 'period');

      if (!empty($filters['since'])) 
        $stats_query->where('timestamp', '>=', $filters['since']);
      
      foreach ($stats_query->get()->groupBy('metric_id') as $metric_id => $stats) 
        $this->find($metric_id)->measurementStats = $stats;
      
      return $this;
    
  

最后,获取带有统计信息的指标

$data = Metric::all()->addMeasrementStats(['since' => '2019-07-31']);

【讨论】:

【参考方案2】:

您可以通过向模型添加新属性来实现,该属性是测量值的平均值。

向您的度量模型添加以下行。

protected $appends = array('min', 'max');

public function getMinAttribute()

    return $this->measurements()->where('timestamp', '>=', '20190731')->min('value');



public function getMaxAttribute()

    return $this->measurements()->where('timestamp', '>=', '20190731')->max('value');

现在,您可以将 min 和 max 作为普通模型属性访问,

foreach($metrics as $metric)
    // $metric->max
    // $metric->min

希望你能理解。

【讨论】:

谢谢,但我认为这会产生 N+1 效率问题并且不会按天分组?

以上是关于如何在 Laravel Eloquent 中添加相关的聚合数据?的主要内容,如果未能解决你的问题,请参考以下文章

Laravel - 使用 Eloquent 查询构建器在选择中添加自定义列

如何通过索引将项目添加到 Laravel Eloquent 集合中?

在 laravel 中定义 eloquent 关系时添加额外的约束

如何将参数传递到链式 Laravel Eloquent 关系中的位置

如何在 Laravel/Eloquent 中获得完整的关系

Laravel Eloquent 支持 MariaDb 动态专栏