Eloquent 模型海量更新

Posted

技术标签:

【中文标题】Eloquent 模型海量更新【英文标题】:Eloquent model mass update 【发布时间】:2014-04-21 06:17:39 【问题描述】:

如果我错了,请纠正我,但我认为 Eloquent 模型中不存在大规模更新。

有没有办法在不为每一行发出查询的情况下对数据库表进行大规模更新?

比如有没有静态方法,比如

User::updateWhere(
    array('age', '<', '18'),
    array(
        'under_18' => 1 
        [, ...]
    )
);

(是的,这是一个愚蠢的例子,但你明白了......)

为什么没有实现这样的功能? 如果出现这种情况,只有我会很高兴吗?

我(开发人员)不想像这样实现它:

DB::table('users')->where('age', '<', '18')->update(array('under_18' => 1));

因为随着项目的发展,以后我们可能会要求程序员更改表名,他们无法搜索和替换表名!

有没有这样的静态方法来执行这个操作?如果没有,我们可以扩展Illuminate\Database\Eloquent\Model 类来完成这样的事情吗?

【问题讨论】:

【参考方案1】:

也许这在几年前是不可能的,但在最近的 Laravel 版本中你绝对可以做到:

User::where('age', '<', 18)->update(['under_18' => 1]);

值得注意的是,在调用update之前需要where方法。

【讨论】:

可以确认这一点,与 Laravel 5.2 一起工作,更优雅和“雄辩”的解决方案。 工程 Laravel 5.6 谢谢你。我正在深入研究源代码以检查他们是否真的这样做了! 接受的答案已经过时了,在google中搜索'eloquent mass update'的第一个结果就是这个问题;所以我想这应该是公认的答案。 如果您看这里,这一直是“雄辩”的方式... laravel.com/docs/6.x/eloquent#updates 另外,请记住,您需要确保包含大量更新的属性在模型上的 $fillable 属性中。这让我措手不及几次!【参考方案2】:

对于大规模更新/插入功能,有人提出了要求,但 Taylor Otwell(Laravel 作者)建议用户应该改用 Query Builder。 https://github.com/laravel/framework/issues/1295

您的模型通常应该扩展 Illuminate\Database\Eloquent\Model。然后你自己访问实体,例如,如果你有这个:

<?php
Use Illuminate\Database\Eloquent\Model;

class User extends Model 

    // table name defaults to "users" anyway, so this definition is only for
    // demonstration on how you can set a custom one
    protected $table = 'users';
    // ... code omited ...

更新 #2

您必须求助于查询生成器。为了解决表命名问题,您可以通过 getTable() 方法动态获取它。唯一的限制是您需要先初始化用户类,然后才能使用此功能。您的查询如下:

$userTable = (new User())->getTable();
DB::table($userTable)->where('age', '<', 18)->update(array('under_18' => 1));

这样你的表名就是用户模型中的控制器(如上例所示)。

更新 #1

执行此操作的其他方法(在您的情况下效率不高)是:

$users = User::where('age', '<', 18)->get();
foreach ($users as $user) 
    $user->field = value;
    $user->save();

这样表名保存在用户类中,您的开发人员不必担心。

【讨论】:

你描述的方法我很清楚。我以为我很清楚我想在我的数据库中使用 ONE QUERY 来做这样一件微不足道的事情。想象一下,如果有大约 20K 行需要更新。按照您描述的方式,将执行 20K 查询。更不用说在内存中保存 20K 记录的开销了!! 是的,我也是这样做的。我只是用一个静态函数扩展了 Eloquent 类,比如 `public static function getTableName() return with(new static)->getTable(); ` 我用 Model::getTableName 得到表名我不能接受你的回答,因为它没有解决最初的问题。我仍然会投票,因为您的时间很宝贵,我可以接受:) 谢谢! 第一段涵盖了没有这种方法的答案。我什至提供了问题的链接,您可以在其中看到 Laravel 的作者关闭它并提供使用查询生成器的说明。在 Eloquent 中没有其他方法可以做到这一点。 我猜你是对的,你确实回答了这个问题:) 在 L6 中是可能的:User::where('age', 'update(['field' => request()->value]);【参考方案3】:

对@metamaker 答案的一点修正:

DB::beginTransaction();
     // do all your updates here
        foreach ($users as $user) 
            $new_value = rand(1,10) // use your own criteria
            DB::table('users')
               ->where('id', '=', $user->id)
               ->update(['status' => $new_value  // update your field(s) here
                ]);
        
    // when done commit
DB::commit();

现在您可以在一个数据库事务中进行 100 万次不同的更新

【讨论】:

虽然这很容易理解,并且不会应用部分更新,除非它可以应用全部,但它距离适合大规模更新还有很长的路要走。如上所述,这是 n+1 个查询,但这可以在 1 个查询中完成。 n+1 个查询可能会遇到各种问题,从执行时间问题到防火墙阻塞。【参考方案4】:

如果您需要更新所有数据没有任何条件,请尝试以下代码

Model::query()->update(['column1' => 0, 'column2' => 'New']);

【讨论】:

这正是我想要的,通过批量编辑我们并不总是需要使用 where() 语句。而且由于我们不能将 update() 称为模型的静态方法( Users:update() ),这对我来说很有帮助。短而清晰。【参考方案5】:

使用数据库事务批量更新多个实体。当你的更新函数完成时,事务将被提交,或者如果在两者之间发生异常,则回滚。

https://laravel.com/docs/5.4/database#database-transactions

例如,这就是我在一次批量更新中为文章重新生成具体化路径段 (https://communities.bmc.com/docs/DOC-9902) 的方式:

public function regenerateDescendantsSlugs(Model $parent, $old_parent_slug)
    
        $children = $parent->where('full_slug', 'like', "%/$old_parent_slug/%")->get();

        \DB::transaction(function () use ($children, $parent, $old_parent_slug) 
            /** @var Model $child */
            foreach ($children as $child) 
                $new_full_slug  = $this->regenerateSlug($parent, $child);
                $new_full_title = $this->regenerateTitle($parent, $child);

                \DB::table($parent->getTable())
                    ->where('full_slug', '=', $child->full_slug)
                    ->update([
                        'full_slug' => $new_full_slug,
                        'full_title' => $new_full_title,
                    ]);
            
        );
    

【讨论】:

从我的角度来看最好的答案。我发布并回答了一个更干净的版本。 我认为这不能完全回答这个问题。这将导致在单个事务中运行许多查询,但仍会导致数千个单独的查询。在这种情况下,到数据库的往返时间可能会累加。例如,如果往返时间约为 300 µs,这将增加 6 秒的执行时间,以更新 OP 建议的约 20k 行。 虽然这很容易理解,并且不会应用部分更新,除非它可以应用全部,但它距离适合大规模更新还有很长的路要走。如上所述,这是 n+1 个查询,但这可以在 1 个查询中完成。 n+1 个查询可能会遇到各种问题,从执行时间问题到防火墙阻塞。【参考方案6】:

Laravel 6.*

我们可以更新query上的海量数据如下:

Appointment::where('request_id' , $appointment_request->id)->where('user_id', Auth::user()->id)->where('status', '!=', 'Canceled')->where('id', '!=', $appointment->id)->update(['status' => 'Canceled', 'canceled_by' => Auth::user()->id]);

【讨论】:

【参考方案7】:

laravel 5.8 你可以像这样完成批量更新:

User::where('id', 24)->update (dataAssociativeArray) ;

【讨论】:

大规模更新意味着用一个查询更新多个实体,我认为您的示例没有说明如何做到这一点。【参考方案8】:

同一指令中批量查询和批量更新的另一个工作代码示例:

Coordinate::whereIn('id',$someCoordIdsArray)->where('status','<>',Order::$ROUTE_OPTIMIZED)
       ->update(['status'=>Order::$ROUTE_OPTIMIZED]);

【讨论】:

以上是关于Eloquent 模型海量更新的主要内容,如果未能解决你的问题,请参考以下文章

PHP使用组件构建自己的PHP框架使用 Eloquent 模型进行数据库操作

使用 Laravel 表单模型绑定和复选框更新多对多 Eloquent 关系

Laravel Eloquent 显示后更新

更新附加/分离 Eloquent 关系的时间戳

Laravel 5.4:使用eloquent和fillable字段更新多个表的多个模型

使用 Laravel Eloquent 使用复合键更新表