74

Please correct me if I am wrong, but I think there is no such thing as mass update in an Eloquent model.

Is there a way to make a mass update on the DB table without issuing a query for every row?

For example, is there a static method, something like

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

(yes, it is a silly example but you get the picture...)

Why isn't there such a feature implemented? Am I the only one who would be very happy if something like this comes up?

I (the developers), wouldn't like to implement it like:

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

because as the project grows, we may require the programmers to change the table name in the future and they cannot search and replace for the table name!

Is there such a static method to perform this operation? And if there is not, can we extend the Illuminate\Database\Eloquent\Model class to accomplish such a thing?

ShahinSorkh
  • 599
  • 1
  • 9
  • 23
papas-source
  • 1,221
  • 1
  • 14
  • 20

9 Answers9

125

Perhaps this was not possible a few years ago but in recent versions of Laravel you can definitely do:

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

Worth noting that you need the where method before calling update.

bryceadams
  • 2,182
  • 1
  • 18
  • 16
  • 6
    Can confirm this, works with Laravel 5.2, way more elegant and "eloquent" solution. – Filip Filipovic May 26 '17 at 07:46
  • 1
    Works Laravel 5.6 – Cholowao Apr 04 '18 at 11:12
  • 2
    Thank you for this. I was digging into the source code to check if they actually did that! – ExoticSeagull Feb 14 '19 at 16:04
  • 2
    The accepted answer is outdated, and the first result in google when searching 'eloquent mass update' is this question; so I guess this should be the accepted answer. – HosseyNJF Mar 03 '20 at 15:03
  • This has always been the "eloquent" way of doing it if you look here... https://laravel.com/docs/6.x/eloquent#updates Also, keep in mind that you need to make sure the properties you're mass updating are included in your $fillable property on your model. This has caught me off guard a handful of times! – Drew Mar 08 '20 at 03:06
  • Note that this doesn't fire model events if you are doing `update()` on a User's relationship. – Ali Gajani Apr 09 '21 at 22:13
  • 1
    If you don't have a `where()` method, you may also use `query()` to do a mass update. E.g. `User::query()->update(...)` will update every single row. – ralphjsmit Jan 06 '22 at 16:16
73

For mass update/insert features, it was requested but Taylor Otwell (Laravel author) suggest that users should use Query Builder instead. https://github.com/laravel/framework/issues/1295

Your models should generally extend Illuminate\Database\Eloquent\Model. Then you access the entity iself, for example if you have this:

<?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 ...

Update #2

You have to resort to query builder. To cover table naming issue, you could get it dynamically via getTable() method. The only limitation of this is that you need your user class initialized before you can use this function. Your query would be as follows:

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

This way your table name is controller in User model (as shown in the example above).

Update #1

Other way to do this (not efficient in your situation) would be:

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

This way the table name is kept in users class and your developers don't have to worry about it.

ek9
  • 3,392
  • 5
  • 23
  • 34
  • 3
    I am well aware of the method you describe. I thought I was clear that I want to do such a trivial thing using ONE QUERY in my database. Imagine if there were about 20K rows that need to be updated. With the way you describe, 20K queries will be executed. Not to mention the overhead for keeping in memory 20K records!! – papas-source Mar 16 '14 at 12:21
  • Yes, this is the way I did it also. I just extended the Eloquent Class with a static function like so ` public static function getTableName() { return with(new static)->getTable(); } ` and I get the table name with Model::getTableName I cannot accept your answer though, since it does not address the initial question. Still I will upvote it because your time is precious and I can accept that :) Thank you! – papas-source Mar 16 '14 at 14:32
  • First paragraph covers the answer that there is no such method. I even provided the link to issue where you can see Laravel's author closing it with directions to use query builder. There is no other way to do it in Eloquent. – ek9 Mar 16 '14 at 14:43
  • I guess you are right, you did answer the question :) – papas-source Jan 31 '15 at 16:38
  • In L6 is possible : User::where('age', '<', 18)->update(['field' => request()->value]); – Vit Jun 12 '20 at 15:05
19

A litle correction to @metamaker answer:

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();

Now you can have 1 milion different updates in one DB transaction

OMR
  • 11,736
  • 5
  • 20
  • 35
catalin87
  • 625
  • 6
  • 19
  • Whilst this is simple to understand, and will not apply part of the update unless it can apply all, it is a long way short of being suitable for mass update. As pointed out above, this is n+1 queries, but this could be done in 1 query. n+1 queries could see various issues, ranging from execution time issues to firewall blocking. – SEoF Oct 06 '21 at 13:13
19

If you need to update all data without any condition, try below code

Model::query()->update(['column1' => 0, 'column2' => 'New']);
Irfandi D. Vendy
  • 894
  • 12
  • 20
  • 2
    Exactly this is what I was looking for, with mass edit we don't always need to use where() statement. And Since we cannot call update() as static method of our Model ( Users:update() ) this was helpful answer for me. Short and Clear. – RedaMakhchan Mar 16 '20 at 12:16
5

Use database transactions to update multiple entities in a bulk. Transaction will be committed when your update function finished, or rolled back if exception occurred somewhere in between.

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

For example, this is how I regenerate materialized path slugs (https://communities.bmc.com/docs/DOC-9902) for articles in a single bulk update:

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,
                    ]);
            }
        });
    }
metamaker
  • 2,307
  • 2
  • 19
  • 18
  • Best answer from my point of view. I posted and answer with a cleaner version. – catalin87 Feb 07 '19 at 09:21
  • 1
    I don't think this quite answers the question. This will result in many queries running within a single transaction but it would still result in thousands of individual queries. The round trip time to the database can add up in cases like this. For example if the roundtrip time is around 300 µs this would add 6 seconds to the execution time for the ~20k rows to update that the OP suggested. – Tim Martin Aug 12 '19 at 16:37
  • Whilst this is simple to understand, and will not apply part of the update unless it can apply all, it is a long way short of being suitable for mass update. As pointed out above, this is n+1 queries, but this could be done in 1 query. n+1 queries could see various issues, ranging from execution time issues to firewall blocking. – SEoF Oct 06 '21 at 13:12
4

Laravel 6.*

We can update mass data on query as follow :

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
    ]);
Eduardo Pacheco
  • 504
  • 4
  • 14
1

Another example of working code of the mass query and mass update in same instruction:

Coordinate::whereIn('id',$someCoordIdsArray)->where('status','<>',Order::$ROUTE_OPTIMIZED)
       ->update(['status'=>Order::$ROUTE_OPTIMIZED]);
CodeToLife
  • 3,672
  • 2
  • 41
  • 29
0

From Laravel 8 you can also use upsert which helped me updated multiple rows at once with each rows having different values.

https://laravel.com/docs/8.x/eloquent#upserts

Pierre
  • 675
  • 1
  • 8
  • 18
-3

laravel 5.8 you can accomplish mass update like so:

User::where('id', 24)->update (dataAssociativeArray) ;
Jared Forth
  • 1,577
  • 6
  • 17
  • 32