Laravel Eloquent如何更新含动态键的JSON嵌套字段值
实现方案
Laravel Eloquent 原生不支持在JSON更新路径中使用*通配符匹配动态键,底层MySQL的JSON值修改函数要求传入明确的属性路径,你写的占位符逻辑无法直接执行,可根据你的MySQL版本选择对应实现方式:
方案1:查询后遍历匹配键名更新(兼容MySQL 5.7+,最稳定)
该方案逻辑清晰,不会出现批量误更新问题,是生产环境首选:
- 首先在
Appointment模型中配置字段类型转换,方便直接读取JSON内容:
// app/Models/Appointment.php protected $casts = [ 'time_slots' => 'array', ];
- 核心查询更新逻辑:
// 入参 $doctorId = 1; $dayId = 1; $targetStart = '09:00'; $targetEnd = '09:30'; // 第一步:查询存在对应时段的预约记录 $appointment = Appointment::where('doctor_id', $doctorId) ->where('day_id', $dayId) ->whereRaw('JSON_SEARCH(time_slots, "one", ?, null, "$.*.start") IS NOT NULL', [$targetStart]) ->whereRaw('JSON_SEARCH(time_slots, "one", ?, null, "$.*.end") IS NOT NULL', [$targetEnd]) ->first(); if (!$appointment) { return response()->json(['msg' => '无匹配可预约时段'], 404); } // 第二步:遍历动态时段,找到匹配起止时间的键名 $matchKey = null; foreach ($appointment->time_slots as $key => $slot) { if ($slot['start'] === $targetStart && $slot['end'] === $targetEnd) { $matchKey = $key; break; } } // 第三步:明确路径后更新selected状态 if ($matchKey) { Appointment::where('id', $appointment->id) ->update(["time_slots->{$matchKey}->selected" => true]); }
注意:时间参数必须用字符串格式传入,你示例中
09:00未加引号会被识别为数字,导致匹配逻辑失效。
方案2:单SQL语句批量更新(仅支持MySQL 8.0+)
如果需要批量处理多条记录、不想做PHP层遍历,可以借助MySQL 8.0新增的JSON_TABLE函数实现动态路径匹配更新:
Appointment::where('doctor_id', $doctorId) ->where('day_id', $dayId) ->update([ 'time_slots' => DB::raw(" JSON_SET( time_slots, CONCAT( '$.', ( SELECT jt.slot_key FROM JSON_TABLE( JSON_KEYS(time_slots), '$[*]' COLUMNS (slot_key VARCHAR(50) PATH '$') ) jt WHERE JSON_UNQUOTE(JSON_EXTRACT(time_slots, CONCAT('$.', jt.slot_key, '.start'))) = ? AND JSON_UNQUOTE(JSON_EXTRACT(time_slots, CONCAT('$.', jt.slot_key, '.end'))) = ? LIMIT 1 ), '.selected' ), true ) ") ], [$targetStart, $targetEnd]);
避坑提示
- 不要尝试用通配符直接更新,如果库中存在同时匹配起止时间的重复时段脏数据,通配逻辑会把所有匹配项的
selected都改为true,不符合预约业务的唯一占用逻辑。 - 如果你的MySQL版本低于8.0,不要使用方案2,
JSON_TABLE是MySQL 8.0才新增的函数,低版本运行会直接报语法错误。
内容的提问来源于stack exchange,提问作者Officialozioma
相关产品推荐
相关产品推荐

