如何在Laravel中实现嵌套GroupBy(分组内再分组)
如何实现用户-项目嵌套分组的工时数据查询?
表结构说明
reports表是记录用户每月工作投入的关联表,结构如下:
| id | user_id | project_id | total_effort | created_at |
|---|---|---|---|---|
| 1 | 5 | 232 | 40 | 2023-01-23 |
需求与现有问题
需要获取用户下各项目贡献明细的嵌套结构数据,用于生成Excel表格。目前仅能通过以下查询实现用户级汇总,无法实现「用户→项目」的嵌套分组:
$data = Report::select('user_id') ->selectRaw("SUM(total_effort) as total, DATE_FORMAT(report_for_date, '%b,%Y') new_date") ->groupBy('user_id')->with('userDetail:id,first_name,last_name')->get();
一、Laravel Eloquent 实现嵌套结构
可以通过「多维度分组查询+集合重组」或「关联预加载」两种方式实现嵌套结构:
方法1:先查询用户-项目层级数据,再重组结构
先获取用户与项目的关联汇总数据,再利用Collection的groupBy方法重组为嵌套结构:
// 查询用户-项目-月份的层级汇总数据,关联用户、项目信息 $rawData = Report::select('user_id', 'project_id') ->selectRaw("SUM(total_effort) as total_effort, DATE_FORMAT(report_for_date, '%b,%Y') as report_month") ->groupBy('user_id', 'project_id', 'report_month') // 按用户、项目、月份多维度分组 ->with('userDetail:id,first_name,last_name') ->with('project:id,name') // 若有Project模型,可关联项目名称等信息 ->get(); // 重组为用户嵌套项目的结构 $nestedData = $rawData->groupBy('user_id')->map(function ($userReports) { $userInfo = $userReports->first()->userDetail; return [ 'user' => $userInfo, 'projects' => $userReports->groupBy('project_id')->map(function ($projectReports) { $projectInfo = $projectReports->first()->project ?? null; return [ 'project' => $projectInfo, 'monthly_records' => $projectReports->pluck(['total_effort', 'report_month']) ]; }) ]; });
最终$nestedData即为外层用户、内层项目的嵌套结构,可直接循环生成Excel。
方法2:利用模型关联预加载处理
若User模型已定义reports关联,可通过预加载时的闭包实现分组:
// 在User模型中定义关联 public function reports() { return $this->hasMany(Report::class); } // 查询用户并预加载按项目分组的工时数据 $users = User::with(['reports' => function ($query) { $query->select('user_id', 'project_id') ->selectRaw("SUM(total_effort) as total, DATE_FORMAT(report_for_date, '%b,%Y') as new_date") ->groupBy('project_id', 'new_date'); }])->get();
返回的每个User实例中,reports属性已按项目分组,可直接遍历使用。
二、原生MySQL查询实现多维度分组
原生MySQL无法直接返回嵌套结构,但可以通过多字段分组获取扁平的层级数据,再在应用层重组:
SELECT r.user_id, u.first_name, u.last_name, r.project_id, p.name AS project_name, SUM(r.total_effort) AS total_effort, DATE_FORMAT(r.report_for_date, '%b,%Y') AS report_month FROM reports r LEFT JOIN users u ON r.user_id = u.id LEFT JOIN projects p ON r.project_id = p.id GROUP BY r.user_id, r.project_id, report_month ORDER BY r.user_id, r.project_id;
该查询返回「用户-项目-月份」的扁平汇总数据,你可以在代码中循环遍历,将相同user_id的记录归为一组,再在每组内按project_id二次分组,最终形成嵌套结构。
内容的提问来源于stack exchange,提问作者user7747472
相关产品推荐
相关产品推荐

