Laravel中如何通过user_id与week匹配更新Challange表数据?
背景
我正在开发一款减重挑战应用,用户可发起挑战比拼减重成果。现有一张challange表,包含id、user_id(外键)、weight和week字段。为在前端按周展示数据,我构建了对应数组,UserController代码如下:
public function index() { $users = User::all(); $challanges = Challange::orderBy('week', 'DESC')->get(); $weightArray = array(); $startweek = 1; $endweek = Challange::max('week'); for ($i = $startweek; $i <= $endweek; $i++) { foreach ($users as $user) { $challenge = $user->challanges()->where('week', '=', $i)->get(); if (count($challenge) > 0) { $weightArray[$i][$user->id] = $challenge[0]->weight; } else { $weightArray[$i][$user->id] = 0; } } } return view('dashboard', compact('users', 'weightArray')); }
用户登录后点击更新,表单传入Auth::user()->id、week及自行输入的weight,但不知如何通过这两个字段匹配到需更新的记录。当前ChallangeController代码:
public function update(Request $request) { $week = $request->week; $challange = Challange::find($request->user_id)->where('week', $week); dd($challange); }
执行报错:Error Call to a member function where() on null。请教是提前在数组加id,还是叠加where条件?
问题根源
你踩了一个Laravel查询的常见坑:find()方法是根据模型的**主键字段(默认是id)**查询记录的,而你传入的是user_id——这显然不是challange表的主键,所以这个查询大概率返回null,之后调用where()自然就触发了"Call to a member function where() on null"的错误。
正确解决方案
你需要通过叠加where条件来匹配user_id和week这两个字段,找到对应的挑战记录后再进行更新。这里有两种实用的处理方式:
1. 手动查询+更新/创建
先通过双条件找到记录,存在则更新,不存在可选择创建新记录或提示用户:
public function update(Request $request) { // 先做参数验证,确保数据合法 $request->validate([ 'user_id' => 'required|exists:users,id', 'week' => 'required|integer|min:1', 'weight' => 'required|numeric|min:0' ]); // 同时匹配user_id和week字段 $challange = Challange::where('user_id', $request->user_id) ->where('week', $request->week) ->first(); if ($challange) { // 找到记录,更新体重 $challange->update(['weight' => $request->weight]); return redirect()->back()->with('success', '体重更新成功'); } else { // 没找到记录,自动创建新的周记录 Challange::create([ 'user_id' => $request->user_id, 'week' => $request->week, 'weight' => $request->weight ]); return redirect()->back()->with('success', '该周记录不存在,已为你创建并更新'); } }
2. 用updateOrCreate简化逻辑
如果你需要实现"有则更新,无则创建"的逻辑,Laravel提供的updateOrCreate方法可以一步搞定,代码更简洁:
public function update(Request $request) { $request->validate([ 'user_id' => 'required|exists:users,id', 'week' => 'required|integer|min:1', 'weight' => 'required|numeric|min:0' ]); // 第一个数组是查询条件,第二个数组是要更新/创建的字段 Challange::updateOrCreate( ['user_id' => $request->user_id, 'week' => $request->week], ['weight' => $request->weight] ); return redirect()->back()->with('success', '体重记录更新成功'); }
这个方法会自动查找匹配user_id和week的记录:存在则更新weight,不存在则创建新记录,非常高效。
额外优化建议
你的UserController中构建weightArray的循环存在N+1查询问题,可以通过预加载优化:
public function index() { // 预加载所有用户的挑战记录,减少数据库查询次数 $users = User::with(['challanges' => function($query) { $query->orderBy('week'); }])->get(); $endweek = Challange::max('week') ?? 1; $weightArray = []; for ($i = 1; $i <= $endweek; $i++) { foreach ($users as $user) { // 从预加载的记录中快速找到对应周的体重 $challenge = $user->challanges->where('week', $i)->first(); $weightArray[$i][$user->id] = $challenge ? $challenge->weight : 0; } } return view('dashboard', compact('users', 'weightArray')); }
这样能大幅减少数据库请求,提升页面加载效率。
内容的提问来源于stack exchange,提问作者Jeffrey van der Kruit

