Laravel 6中如何批量实现基于id和condition的增改操作?
Laravel 6 批量实现新增/更新(匹配id和condition)
核心限制说明
Laravel 6的Eloquent并没有内置upsert方法(该方法从Laravel 8才开始引入),所以无法直接通过Eloquent的单次调用完成批量的「新增或更新」操作,但可以通过以下两种方式优化你的实现:
方式一:优化现有循环,减少数据库查询次数
你当前的写法存在一个小问题:$id和$condition没有从循环的$data中提取,先修正这个基础写法,再优化查询逻辑:
修正后的基础循环写法
foreach ($data_arr as $data) { Model::updateOrCreate( ['id' => $data['id'], 'condition' => $data['condition']], $data ); }
但这种写法每条数据都会触发一次「查询+插入/更新」,数据量大时效率偏低。可以改成先批量查询已存在的记录,再分批次处理插入和更新:
// 1. 提取所有要匹配的id+condition组合 $identifiers = collect($data_arr)->map(function($item) { return [$item['id'], $item['condition']]; }); // 2. 批量查询数据库中已存在的记录,用唯一键组合作为数组键 $existingKeys = Model::where(function($query) use ($identifiers) { foreach ($identifiers as [$id, $condition]) { $query->orWhere(function($q) use ($id, $condition) { $q->where('id', $id)->where('condition', $condition); }); } })->get()->mapWithKeys(function($item) { return [$item->id . '|' . $item->condition => $item]; }); // 3. 拆分出需要插入和更新的数据 $insertData = []; $updateItems = []; foreach ($data_arr as $data) { $key = $data['id'] . '|' . $data['condition']; if (isset($existingKeys[$key])) { $updateItems[] = array_merge( ['id' => $data['id'], 'condition' => $data['condition']], $data ); } else { $insertData[] = $data; } } // 4. 批量插入新数据 if (!empty($insertData)) { Model::insert($insertData); } // 5. 批量更新已有数据(用CASE WHEN实现高效批量更新) if (!empty($updateItems)) { $casesCol1 = collect($updateItems)->map(function($item) { return "WHEN id = ? AND condition = ? THEN ?"; })->implode(' '); $casesCol2 = collect($updateItems)->map(function($item) { return "WHEN id = ? AND condition = ? THEN ?"; })->implode(' '); // 整理绑定参数 $bindings = []; foreach ($updateItems as $item) { $bindings[] = $item['id']; $bindings[] = $item['condition']; $bindings[] = $item['col1']; } foreach ($updateItems as $item) { $bindings[] = $item['id']; $bindings[] = $item['condition']; $bindings[] = $item['col2']; } foreach ($updateItems as $item) { $bindings[] = $item['id']; $bindings[] = $item['condition']; } DB::update(" UPDATE your_table SET col1 = CASE $casesCol1 ELSE col1 END, col2 = CASE $casesCol2 ELSE col2 END WHERE (id, condition) IN (" . collect($updateItems)->map(fn() => '(?, ?)')->implode(',') . ") ", $bindings); }
这种方式将原来的N次查询减少为1次查询,再通过批量插入和批量更新完成操作,效率比原始循环高很多。
方式二:用原生SQL的INSERT ... ON DUPLICATE KEY UPDATE(推荐)
这是效率最高的方式,需要先给表添加id和condition的联合唯一索引,让MySQL能识别冲突行:
1. 添加联合唯一索引
ALTER TABLE your_table ADD UNIQUE INDEX idx_id_condition (id, condition);
2. 构造批量SQL语句
用DB门面执行原生SQL,通过参数绑定避免注入问题:
// 定义要操作的字段列 $columns = ['id', 'condition', 'col1', 'col2']; // 准备绑定参数和占位符 $bindings = []; $placeholders = collect($data_arr)->map(function($item) use (&$bindings, $columns) { $rowPlaceholders = []; foreach ($columns as $col) { $rowPlaceholders[] = '?'; $bindings[] = $item[$col]; } return '(' . implode(',', $rowPlaceholders) . ')'; })->implode(','); // 构造更新子句:冲突时更新指定字段 $updateClause = collect($columns) ->filter(fn($col) => !in_array($col, ['id', 'condition'])) ->map(fn($col) => "$col = VALUES($col)") ->implode(','); // 执行批量插入/更新 DB::statement( "INSERT INTO your_table (" . implode(',', $columns) . ") VALUES $placeholders ON DUPLICATE KEY UPDATE $updateClause", $bindings );
这种方式只需要一次数据库请求就能完成所有数据的新增或更新,效率最优。
内容的提问来源于stack exchange,提问作者pileup
相关产品推荐
相关产品推荐

