如何在Eloquent中对关联模型的自定义属性使用聚合函数?
解决Eloquent中自定义属性的聚合计算问题
withAvg这类Eloquent聚合方法是直接生成SQL在数据库层面执行的,只能识别数据库实际存在的字段,没法直接用模型的自定义属性。下面给两种可行的解决思路:
思路一:把自定义属性的计算逻辑移到数据库层面
如果你的duration是基于Command模型的数据库字段计算出来的(比如用start_time和end_time相减得到),直接把计算逻辑写到SQL里就行,两种实现方式:
方式1:用子查询构造平均值计算
$projects = Project::with('tasks.commands') ->addSelect([ 'average_duration' => Command::selectRaw('AVG(end_time - start_time)') ->whereIn('task_id', function ($query) { $query->select('id')->from('tasks')->whereColumn('project_id', 'projects.id'); }) ]) ->get();
方式2:借助HasManyThrough关联
先在Project模型里定义和Command的间接关联:
// Project.php public function commands() { return $this->hasManyThrough(Command::class, Task::class); }
然后直接用withAvg,把计算逻辑传入:
$projects = Project::with('tasks.commands') ->withAvg('commands', DB::raw('end_time - start_time')) ->get();
这样得到的commands_avg_duration就是你要的平均值。
思路二:内存中手动计算(适合数据量小的场景)
如果duration的计算逻辑特别复杂,没法用SQL实现(比如依赖其他自定义属性、外部接口数据),那就只能先把所有数据拉到内存里再计算:
// 加载所有关联数据 $projects = Project::with('tasks.commands')->get(); // 逐个项目计算平均值 $projects->each(function ($project) { // 提取所有有效的duration值 $validDurations = $project->tasks ->flatMap(function ($task) { return $task->commands->pluck('duration'); }) ->filter(); // 过滤掉空值 // 赋值平均值,空的话设为0避免报错 $project->average_duration = $validDurations->isEmpty() ? 0 : $validDurations->avg(); });
注意:这种方法会把所有tasks和commands都加载到内存,数据量大的时候性能会受影响,谨慎使用。
内容的提问来源于stack exchange,提问作者vbulash
相关产品推荐
相关产品推荐

