0

I am trying to find someway to update multiple records at once like we have insert for creating multiple records at once.

Lets say I have an associated array which contains data to be updated in each row of a table employees:

$data = [
  ['id'=>1,'name'=>'xxx'],
  ['id'=>2,'name'=>'xxx'],
  ['id'=>3,'name'=>'xxx'],
  ['id'=>4,'name'=>'xxx']
];

One way of updating all these records is:

foreach($data as $d){
   Employee::where('id'=>$d['id'])->update(['name'=>$d['name']]);
}

where Employee is model for employees table ofocurse.

I am wondering if i can update all records by one line statement?

For instance if I had create new records from $data, I would use:

Employee::insert($data);

instead of looping through $data and use create() for each record.

Anything like that exists for updation as well?

Abhay Maurya
  • 11,819
  • 8
  • 46
  • 64

9 Answers9

0

No, it's not possible, you should iterate over the array and update each row separately.

Alexey Mezenin
  • 158,981
  • 26
  • 290
  • 279
  • @Learner you can create your own helper to handle this and pass data to it, like `->updateMultiple($data)`, but this code will still generate separate query to DB for each update operation. – Alexey Mezenin Mar 27 '17 at 11:15
  • @ Alexey Mezenin, yes you are right, just to distribute the code in separate files is not what i was looking for. But thanks for your time. – Abhay Maurya Mar 27 '17 at 11:20
0

Did you try Employee::update($data);? of course data should be as array like: $data = (['name'=>'test'],['surname'=>'test2'])); - it will update all the rows in the table - but you can add an where condition.

  • :))) Ok I try again to explain here. I dont know why it was so unclear to you. I want to update MULTIPLE rows in one shot. The way you are suggesting is to update ONE row at a time. I have proposed a solution in question itself to update one row at a time but thats NOT what i want. I want to update more than one row in one shot. Same like `insert()` creates more than one row in one shot. I was curious if some native laravel function exists to do same but for updating NOT creating. In other way, what you are proposing is`create()`equivalent for creating new row, I wanna have`insert()`equivalen – Abhay Maurya Mar 27 '17 at 12:05
  • Please look here: https://laracasts.com/discuss/channels/general-discussion/update-multiple-records-using-eloquent?page=1 – Marcin Gierus Mar 27 '17 at 12:28
  • I really appreciate your effort. But as you might notice in my question, I need to update multiple rows with different id NOT same id. Plus i have different `name` field for every row NOT same so your link doesnt work in my case because: 1. They have same id they wanna change rows of: item_type_id, 2. They have same value they wanna update rows in: 'colour' => 'black'. I hope you understand. – Abhay Maurya Mar 27 '17 at 12:37
  • 1
    :P I wont let it go, if you have diferent id you can use laravels whereIn with update, for example : `$users = User::whereIn('id', array(1, 2, 3))->update($data); ` dont know if whereIn could be used with two parameters :D – Marcin Gierus Mar 27 '17 at 12:42
  • Maybe its how Alexey Mezenin said, its just simply not possible. But I am glad you got the problem and still trying. Cheers. – Abhay Maurya Mar 27 '17 at 12:46
0

Doctrine allows you to do batch inserts/updates. You can find it here: http://www.laraveldoctrine.org/

z1haze
  • 431
  • 4
  • 7
  • Thanks, but i dont want to install full extension just to avoid a foreach because i anyway have `insert()` to insert multiple data. I was hoping maybe anyone knows some laravel native function or more efficient way to do so. I am better off using one foreach instead of adding extra extension. – Abhay Maurya Mar 27 '17 at 12:15
0

What about doing something like this, using collections.

$data = [
  ['id'=>3,'name'=>'xxx'],
  ['id'=>4,'name'=>'xxx'],
  ['id'=>5,'name'=>'xxx']
];

Employee::find(collect($data)->pluck('id')->toArray())->map(function($item, $key) use ($data){
  $item['name'] = $data[$key]['name'];
  return $item->save();
});
Yat23
  • 181
  • 4
  • Ah, I just realised that this will only work if the ids are sorted beforehand. – Yat23 Mar 28 '17 at 14:45
  • Its a good solution. Although problem is that it will take even more time than a simple foreach statement if you analyze your construct. Its not more efficient than foreach method. – Abhay Maurya Mar 30 '17 at 11:33
  • Agreed, it is an alternative though. I don't think there is a simpler solution than a foreach, because if it was possible to do it with plain SQL, Eloquent would be able to. – Yat23 Mar 30 '17 at 13:44
  • Well, as a matter of fact, it is possible in raw query http://stackoverflow.com/questions/20255138/sql-update-multiple-records-in-one-query but unfortunately not implemented in eloquent in laravel. – Abhay Maurya Mar 30 '17 at 14:00
  • Ah I see, makes sense. You could then dynamically build the query, but that will probably not be simpler than a foreach. – Yat23 Mar 30 '17 at 14:35
  • yes it will not be..and i guess Laravel didnt do it behind scenes and make a simple function to call for it because union is in general very slow. – Abhay Maurya Mar 31 '17 at 10:52
0

Its not possible because updation in bulk can only be possible via union which is the most slow operator you can use in sql. Otherwise there is no way to update in bulk with different values in different rows even in raw sql and therefore no way in laravel as well.

0

I wrote a batch update function to use in my Laravel projects. It may be useful for anyone who wants to use bulk update query in laravel. Its first parameter is the table name string, second is the key name string on which you wants to update the row or rows and mostly it will be the 'id', the third parameter is the data array in the format like:

array(
    array(
        'id' => 1,
        'col_1_name' => 'col_1_value',
        'col_2_name' => 'col_2_value',
        //....
    ),
    array(
        'id' => 2,
        'col_1_name' => 'col_1_value',
        'col_2_name' => 'col_2_value',
        //....
    ),
    //....
);

Function definition:

private function custom_batch_update(string $table_name = '', string $key = '', Array $update_arr = array()) {

    if(!$table_name || !$key || !$update_arr){
        return false;
    }

    $update_keys = array_keys($update_arr[0]);
    $update_keys_count = count($update_keys);

    for ($i = 0; $i < $update_keys_count; $i++) {
        $key_name = $update_keys[$i];
        if($key === $key_name){
            continue;
        }
        $when_{$key_name} = $key_name . ' = CASE';
    }

    $length = count($update_arr);
    $index = 0;
    $query_str = 'UPDATE ' . $table_name . ' SET ';
    $when_str = '';
    $where_str = ' WHERE ' . $key . ' IN(';

    while ($index < $length) {
        $when_str = " WHEN $key = '{$update_arr[$index][$key]}' THEN";
        $where_str .= "'{$update_arr[$index][$key]}',";
        for ($i = 0; $i < $update_keys_count; $i++) {
            $key_name = $update_keys[$i];
            if($key === $key_name){
                continue;
            }
            $when_{$key_name} .= $when_str . " '{$update_arr[$index][$key_name]}'";
        }
        $index++;
    }

    for ($i = 0; $i < $update_keys_count; $i++) {
        $key_name = $update_keys[$i];
        if($key === $key_name){
            continue;
        }
        $when_{$key_name} .= ' ELSE ' . $key_name . ' END, ';
        $query_str .= $when_{$key_name};
    }
    $query_str = rtrim($query_str, ', ');
    $where_str = rtrim($where_str, ',') . ')';
    $query_str .= $where_str;
    $affected = DB::update($query_str);

    return $affected;
}

It will produce and execute the query string like this:

UPDATE table_name SET col_1_name = CASE 
WHEN id = '1' THEN 'col_1_value' 
WHEN id = '2' THEN 'col_1_value' 
ELSE col_1_name END, 
col_2_name = CASE 
WHEN id = '1' THEN 'col_2_value' 
WHEN id = '2' THEN 'col_2_value' 
ELSE col_2_name END 
WHERE id IN('1','2')
0

It will work in laravel to update existing or new insertion into table.

$data=array(  
    array('id'=>1,'name'=>'kaleemullah@example.com'),
    array('id'=>2,'name'=>'kaleemullah@example.com2'),
    array('id'=>4,'name'=>'kaleemullah@example.com4')
);
$imported =  implode(', ', array_map(function ($string) {
return "(".$string['id'].','."'".$string['name']."'"."),";
}, $data));
$OneStepAway =  str_replace(',,', ',', $imported);
$kale = rtrim($OneStepAway,',');
$s = DB::statement("INSERT INTO  tests (id,name) VALUES $kale
ON DUPLICATE KEY UPDATE name=VALUES(name)") ;
echo "Successfully Updated and Insert New records into the database ";
Kaleemullah
  • 446
  • 3
  • 8
0

For a small chunk of data, a loop would do fine.

However, if you have lots of data - definitely stay away from looping because "database transaction" is an expensive operation and with a for loop, you'll end up doing a transaction for each iteration.

Here you can see how to update without a for loop:

$data = [
  ['id'=>1,'name'=>'xxx'],
  ['id'=>2,'name'=>'xxx'],
  ['id'=>3,'name'=>'xxx'],
  ['id'=>4,'name'=>'xxx']
];

$cases = [];
$ids = [];
$params = [];

foreach ($data as $datum) {
   $id = $datum['id'];

   $cases[] = "WHEN {$id} then ?";
   $params[] = $datum['name'];
   $ids[] = $id;
}

$ids = implode(',', $ids);
$cases = implode(' ', $cases);

if (!empty($ids)) {
    \DB::update("UPDATE employees SET `name` = CASE `id` {$cases} END WHERE `id` in ({$ids})", $params);
}

If you want to read about it, check https://medium.com/@sentiasa/bulk-update-multiple-records-with-separate-data-laravel-3da9131c279a

senty
  • 12,385
  • 28
  • 130
  • 260
0
public function transaction($callback) {
    DB::connection(**<TABLE_NAME>**)->transaction($callback);
}

public function updateBatch($batchData)
{
    $count = 0;
    try {
        $batchData = array_values($batchData);
        $total = count($batchData);
        $j = 0;
        $i = 0;
        do {
            $j = min($total -1, $j + self::$configBatch);
            DB::connection(**<TABLE_NAME>**)->transaction(function() use ($batchData, &$count, $total, $j, &$i) {
                for (; $i <= $j; $i++) {
                    $batchData[$i]->save();
                    $count++;
                }
            });
        } while ($i < $total);
    } catch (\Exception $exception) {
        print_r($exception->getMessage());
        $count = 0;
    }
    return $count;
}

Hope to help you

Kieu Trung
  • 159
  • 1
  • 7