Laravel 5.5集合字段添加及管理员用户培训统计优化问询
Laravel 5.5 相关问题解答
1. 如何在Laravel 5.5中从第二个集合向目标集合添加字段?
根据两个集合的关联方式,有几种实用的处理方法:
场景1:两个集合通过唯一标识(如user_id)关联
如果第二个集合里的元素和目标集合元素有对应的唯一标识,比如$users(目标集合)和$trainingStats(第二个集合,包含user_id、todo、done字段),可以先把第二个集合转成关联数组,再用map批量添加字段:
// 将统计集合转为以user_id为键的关联数组,方便快速查找 $statsMap = $trainingStats->keyBy('user_id'); // 遍历用户集合,为每个用户添加统计字段 $usersWithStats = $users->map(function ($user) use ($statsMap) { // 若用户无对应统计,默认todo和done为0 $stats = $statsMap->get($user->id, (object) ['todo' => 0, 'done' => 0]); $user->todo = $stats->todo; $user->done = $stats->done; return $user; });
场景2:两个集合元素按顺序一一对应
如果两个集合的元素顺序完全匹配(比如索引对应),可以直接用zip方法合并后处理:
$usersWithStats = $users->zip($trainingStats)->map(function ($pair) { list($user, $stats) = $pair; $user->todo = $stats->todo ?? 0; $user->done = $stats->done ?? 0; return $user; });
2. 下属用户培训统计的查询优化方案
你当前的写法属于典型的N+1查询问题——先查1次管理员下属用户,再循环每个用户执行2次SQL,用户数量越多,性能损耗越严重。这里给你几个优化方案:
方案一:Eloquent关联+预加载统计(最推荐,符合Laravel开发习惯)
首先确保User模型和培训记录模型(假设为TrainingRecord)定义了关联:
// app/User.php public function trainingRecords() { return $this->hasMany(TrainingRecord::class); }
然后利用withCount方法,在查询用户时一次性统计待完成和已完成的数量:
$admin = Admin::findOrFail(Auth::guard('admin')->user()->id); $users = $admin->users() ->withCount([ // 统计待完成培训数量,别名todo 'trainingRecords as todo' => function ($query) { $query->where('status', 'todo'); // 替换为你实际的待完成状态判断 }, // 统计已完成培训数量,别名done 'trainingRecords as done' => function ($query) { $query->where('status', 'done'); // 替换为你实际的已完成状态判断 } ]) ->get();
查询完成后,每个$user对象会自动拥有todo和done属性,直接调用$user->todo即可获取统计数,全程仅需2次SQL查询。
方案二:JOIN查询一次性获取数据
如果培训统计逻辑复杂,或者不想依赖Eloquent关联,可以用原生JOIN拼接查询:
$users = $admin->users() ->leftJoin(DB::raw('(SELECT user_id, COUNT(*) as todo FROM training_records WHERE status = "todo" GROUP BY user_id) as todo_stats'), function ($join) { $join->on('users.id', '=', 'todo_stats.user_id'); }) ->leftJoin(DB::raw('(SELECT user_id, COUNT(*) as done FROM training_records WHERE status = "done" GROUP BY user_id) as done_stats'), function ($join) { $join->on('users.id', '=', 'done_stats.user_id'); }) ->select('users.*', DB::raw('COALESCE(todo_stats.todo, 0) as todo'), DB::raw('COALESCE(done_stats.done, 0) as done')) ->get();
COALESCE函数用于处理无培训记录的用户,默认统计数为0,避免返回null。
方案三:批量查询后关联集合
如果已经通过其他方式获取了所有用户的统计数据集合,可以参考第一个问题的方法,将统计数据转成关联数组后批量添加到用户集合,同样能避免循环查询。
内容的提问来源于stack exchange,提问作者davidvera
相关产品推荐
相关产品推荐

