Laravel 中实现 MySQL 批量更新的两种方式:case when 与 join

示例数据

假设有一个 users 表,结构如下:

id name status
1 Alice active
2 Bob active
3 Charlie inactive
4 Dave inactive

现在要实现如下的批量更新:

  • id 为 1 的用户状态更新为 inactive
  • id 为 2 的用户状态更新为 suspended
  • id 为 3 的用户状态更新为 active
  • id 为 4 的用户状态更新为 suspended

假设更新条件存储在数组 $updates 中:

$updates = [
    ['id' => 1, 'status' => 'inactive'],
    ['id' => 2, 'status' => 'suspended'],
    ['id' => 3, 'status' => 'active'],
    ['id' => 4, 'status' => 'suspended'],
];

方法一:使用 case when 实现批量更新

case when 语句适合较少数据的批量更新操作,可以根据 id 的值选择性更新每行的数据。

实现代码

  1. 构造 case when 语句:遍历数组,将 id 与对应的 status 值拼接成 when 条件。
  2. 生成完整 SQL:使用 case when 条件更新,限定更新范围。
use Illuminate\Support\Facades\DB;

$sql = 'update users set status = case id';
foreach ($updates as $update) {
    $sql .= sprintf('when %d then %s ', $update['id'], $update['status');
}
$sql .= sprintf( 'else status end where id in (%s)', implode(',', array_column($updates, 'id')) );

DB::update($sql);
// 或: DB::statement($sql);

生成的 SQL 如下:

update users
set status = case id
                 when 1 then 'inactive'
                 when 2 then 'suspended'
                 when 3 then 'active'
                 when 4 then 'suspended'
                 else status end
where id in (1, 2, 3, 4);

优缺点

  • 优点:简单清晰,适合小批量更新。
  • 缺点:更新多字段时处理较麻烦, 数据量较大时 SQL 会变得冗长,性能不如 join 高。

方法二:使用 join union all 实现批量更新

join union all 适合较大数据量的批量更新。它通过虚拟表更新匹配的字段,性能更优。

实现代码

  1. 构造 union all 子查询:将 $updates 数组转换为 union all 子查询结构。
  2. 生成完整 SQL:使用 join 将主表与子查询连接,进行更新操作。
use Illuminate\Support\Facades\DB;

$union = [];
foreach ($updates as $update){
    $union[] = sprintf("select %s as id, '%s' as status", $update['id'], $update['status']);
}
$union = implode(' union all ', $union);
$ids   = implode(',', array_column($updates, 'id'));

$sql = <<<SQL
update users as u
    join ({$union}) as updates
on u.id = updates.id
    set u.status = updates.status
where u.id in ({$ids})
SQL;

DB::update($sql);
// 或: DB::statement($sql);

生成的 SQL 如下:

update users as u
    join (select 1 as id, 'inactive' as status
          union all
          select 2 as id, 'suspended' as status
          union all
          select 3 as id, 'active' as status
          union all
          select 4 as id, 'suspended' as status) as updates on u.id = updates.id
set u.status = updates.status
where u.id in (1, 2, 3, 4);

优缺点

  • 优点:性能较高,适合大量数据批量更新。
  • 缺点:代码稍微复杂一些,但 SQL 结构更加清晰。

总结

方法 优点 缺点
case when 简单清晰,适合少量数据的批量更新 更新多字段时处理较麻烦, 数据量较大时 SQL 会变得冗长
join union all 性能优,适合大批量/多字段数据更新 代码稍复杂,需要构造子查询

根据数据量和需求选择合适的方式:case when 更适合简单的、小数据量的批量更新;而 join union all 在数据量大、更新字段较多时更高效。

注意:以上代码仅仅供参考, 使用是需要结合实际场景进行修改。(比如: 数据量过大时需要分批处理)

本作品采用《CC 协议》,转载必须注明作者和本文链接
啪嗒啪嗒啪嗒 (`・ω・´)つ_▃ <?php echo "PHP is the best language in the world!"; ?>
slowlyo
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 9

方法挺好!当我需要这样更新时,我会选择分批更新更直观。

3个月前 评论
slowlyo (楼主) 3个月前

还是foreach直观

3个月前 评论
slowlyo (楼主) 3个月前

博客:Laravel 内批量更新数据 推量更新推荐这个库,不用自己手写那么多复杂的

3个月前 评论
slowlyo (楼主) 3个月前
raybon (作者) 3个月前

直接 upsert 不行吗

3个月前 评论
slowlyo (楼主) 3个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!