You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel Eloquent深度嵌套关系中withSum的使用问题

Laravel中统计Sprint关联任务总时长的最优方案

我在Laravel聚合关系文档里没找到合适的方案,之前已经用withSum实现了按条件统计用户关联的taskTimeSessions时长,代码如下:

private function refreshUsers()
{
    $this->users = User::withSum(['taskTimeSessions'=> function ($query) {
                                $query->whereMonth('created_at',$this->month)
                                        ->where('is_reconciled',1);
                    }],'session_duration_in_seconds')
                    ->get();
}

需求场景

现在需要统计Sprint的总时长,或者至少获取Sprint内各Task的时长以便求和,模型关系如下:

  • Sprint 关联多个 SprintTasks(中间表)
  • SprintTask 属于一个 Task
  • Task 关联多个 TaskTimeSessions

尝试过的方法

  1. 嵌套withSum查询,执行无报错但无法找到统计结果:
Sprint::with([
'sprintTasks.task'=> function ($query) {
    $query->withSum('taskTimeSessions','session_duration_in_seconds');
   }])
->get();
  1. 尝试多级关联直接调用withSum,触发错误:
->withSum('sprintTasks.task.taskTimeSessions', 'session_duration_in_seconds')

错误信息:

Call to undefined method App\Models\Sprint::sprintTasks.task()

相关模型代码

Sprint模型

public function sprintTasks()
{
    return $this->hasMany(SprintTask::class, 'sprint_id');
}

SprintTask模型

protected $fillable = [
    'sprint_id',
    'task_id',
    'is_completed'
];

public function task()
{
    return $this->belongsTo(Task::class,'task_id');
}

public function sprint()
{
    return $this->belongsTo(Task::class,'sprint_id');
}

注意:此处sprint()关联存在错误,应指向Sprint::class而非Task::class,后续方案会修正该问题

Task模型

public function taskTimeSessions()
{
    return $this->hasMany(TaskTimeSession::class, 'task_id');
}

TaskTimeSession模型

protected $fillable = [
    'task_id',
    'session_duration_in_seconds'
];

public function task()
{
    return $this->belongsTo(Task::class,'task_id');
}

当前临时方案

目前只能在Blade模板中通过循环求和,实现不够优雅:

@php
    $timeTracked = 0;
    
    foreach ($sprint->sprintTasks as $sprintTask) {
        $timeTracked += $sprintTask->task->time_tracked_in_seconds;
    }
@endphp

最优方案推荐

方案一:修正关联后封装模型统计方法

首先修正SprintTask模型的关联错误:

public function sprint()
{
    return $this->belongsTo(Sprint::class,'sprint_id');
}

然后在Sprint模型中封装统计方法,直接通过关联查询求和:

public function totalTaskTime()
{
    return $this->sprintTasks()
        ->join('tasks', 'sprint_tasks.task_id', '=', 'tasks.id')
        ->join('task_time_sessions', 'tasks.id', '=', 'task_time_sessions.task_id')
        ->sum('task_time_sessions.session_duration_in_seconds') ?? 0;
}

使用时直接调用$sprint->totalTaskTime()即可获取总时长。

方案二:查询时直接添加统计字段(高效推荐)

在查询Sprint时,通过子查询直接统计总时长,避免后续循环计算:

$sprints = Sprint::addSelect(['total_task_time' => function ($query) {
    $query->selectRaw('SUM(session_duration_in_seconds)')
        ->from('task_time_sessions')
        ->join('tasks', 'tasks.id', '=', 'task_time_sessions.task_id')
        ->join('sprint_tasks', 'sprint_tasks.task_id', '=', 'tasks.id')
        ->whereColumn('sprint_tasks.sprint_id', 'sprints.id');
}])->get();

每个Sprint实例会直接携带total_task_time属性,直接访问即可。

方案三:修正嵌套withSum的访问方式

如果需要保留各Task的单独时长统计,可使用嵌套withSum,但需注意正确访问统计字段:

$sprints = Sprint::with(['sprintTasks.task' => function ($query) {
    $query->withSum('taskTimeSessions', 'session_duration_in_seconds');
}])->get();

访问总时长时:

foreach ($sprints as $sprint) {
    $total = 0;
    foreach ($sprint->sprintTasks as $st) {
        $total += $st->task->task_time_sessions_sum_session_duration_in_seconds ?? 0;
    }
    // $total即为当前Sprint的总时长
}

内容的提问来源于stack exchange,提问作者Jakub

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 16:20:45