Laravel Bulk Update for multiple record ids - php

I want to mass update my records in Laravel but the records not getting updated. I have a different record for each Id. Below is what I am trying.
$ids = [5,6,8,9],
$updated_array = [
['name' => 'tarun'],
['name' => 'Akash'],
['name' => 'Soniya'],
['name' => 'Shalu'],
];
Model::whereIn('id', $ids)->update($updated_array);

Mass updates are used when you're trying to update multiple rows to the same values. You cannot mass update with different values.
Therefore, this would work, but would update all matching records to name of 'tarun':
Model::whereIn('id', $ids)->update(['name' => 'tarun']);
For your example, you could do:
foreach($ids as $key => $id) {
Model::where('id', $id)->update($updated_array[$key]);
}
But as far as I know, there's no way to do this without running 4 queries in Laravel and writing a raw SQL statement to accomplish this would even be messy.

You can use Laravel Upsert for mass update.
For example :
User::query()->upsert([
['id' => 1, 'email' => 'dadan#example.com'],
['id' => 2, 'email' => 'satria#example.com'],
], 'email');
This feature available in Laravel 8 or newer

Some good solutions for this issue are on the following post: https://github.com/laravel/ideas/issues/575
1) Can create custom function to do this, which uses raw SQL, as per barryvdh comment in the post.
public static function updateValues(array $values)
{
$table = MyModel::getModel()->getTable();
$cases = [];
$ids = [];
$params = [];
foreach ($values as $id => $value) {
$id = (int) $id;
$cases[] = "WHEN {$id} then ?";
$params[] = $value;
$ids[] = $id;
}
$ids = implode(',', $ids);
$cases = implode(' ', $cases);
$params[] = Carbon::now();
return \DB::update("UPDATE `{$table}` SET `value` = CASE `id` {$cases} END, `updated_at` = ? WHERE `id` in ({$ids})", $params);
}
This apparently increase performance by 13x
2) As per another comment, another idea is to do it on a per record basis, but do the whole thing as a single transaction, which makes it much faster.
DB::beginTransaction();
// your loop and updates;
if( !$user )
{
rollbackTransaction();
} else {
// Else commit the queries
commitTransaction();
}
3) There is a laravel library also that appears to try and solve this issue. https://github.com/mavinoo/laravelBatch
Note: I have not tried or tested any of the above solutions.

I think the only way to do this without n queries would be to
(optional) backup table
grab the records and create an array with all the updated data
bulk delete the records
bulk insert from array
That's 3 queries.
Iterating through n records is not practical for my application either; I was hoping there was an alternative but it looks like I'm going to have to implement this.

We can Insert and update batch (bulk) in laravel using this Package
Laravel Batch

Please try the below code:
$data_to_be_updated = [ ['id'=>5,'name' => 'tarun'], ['id'=>6, 'name' => 'Akash'],
['id'=>8, 'name' => 'Soniya'], ['id'=>9,'name' => 'Shalu']
];
foreach ($data_to_be_updated as $key => $value) {
$data = Model::where('id',$value['id'])->first();
if ($data) {
$data->name = $value['name'];
$data->save();
}
}

you can do it like this above mentioned:-
foreach($ids as $key => $id) {
Model::where('id', $id)->update($updated_array[$key]);
}

Related

Is there a way to avoid updating all data when using update_batch()?

My current problem is that all data are being updated all at once when I use update_batch(), is there a way to update only the last data?
I am updating grades every semester that is why I need to update the latest grades in this semester. However whenever I update grades, the latest update will also update the previous grades in my semester that is why it is being overwritten. Hence, I only want to update the latest grades in this semester. I also used update_batch to update multiple records, am I wrong doing so?
Here is my controller:
$schoolYear = $csvData[0]['schoolYear'];
$semester = $csvData[0]['semester'];
$studentIDFromDB = $this->importSISAcademicInformationGetStudentIDFromDB($schoolYear, $semester);
foreach ($csvData as $key => $value) {
if(isset($studentIDFromDB[$value['studentID']])){
$updateFields[$key] = array(
'studentID' => $value['studentID'],
'programType' => $value['programType'],
'level' => $value['level'],
'sectionDescription' => $value['sectionDescription'],
'schoolYear' => $value['schoolYear'],
'semester' => $value['semester']
);
}
}
if(isset($updateFields))
$this->DBLogic->updateBatchRecordMultipleCondition('tbl_tt_academicinfo', $updateFields, 'studentID', array('schoolYear' => $schoolYear, 'semester' => $semester));
Here is my Model:
public function updateBatchRecordMultipleCondition($table, $fields, $criteria1, $criteriaN){
$this->db->where($criteriaN);
$this->db->update_batch($table, $fields, $criteria1);
}
In Codeigniter 3.x, the function batch_update(), as by documentation has its where clause set in the 3rd parameter of the function and not by a preceding where() clause.
without knowing anything else about your variables and data, most likely this will do it:
$this->db->update_batch($table, $fields, $criteriaN);
Anyway, make sure you follow these indications from doc:
update_batch($table[, $set = NULL[, $value = NULL[, $batch_size = 100]]])
Parameters:
$table (string) – Table name
$set (array) – Field name, or an associative array of field/value pairs
$value (string) – Field value, if $set is a single field
$batch_size (int) – Count of conditions to group in a single query
Returns:
Number of rows updated or FALSE on failure
hint: In case you cannot set the where clause in the update_batch() function, use the update() function - which allows you to set a preceding where() - within you foreach loop.

How to update multiple rows in sql from array data using laravel eloquent

I was trying to update multiple records in my database using laravel eloquent but is getting errors when trying to update using an array.
I am not really sure how to correctly get the data from the array to my update function.
The array I am passing looks like this.
My Database table looks like
id | checklistid | categoryid | isCheck | created_at | updated_at
My Controller looks like this.
public function updateCategoryListData(Request $request){
$checklistdata = $request->get('checklist');
$id = $request->get('checklistid');
$dataset = [] ;
foreach($checklistdata as $key =>$value){
$dataset[] = ['checklistid'=>$id,'categoryid' => $key,'isCheck'=>$value];
}
categorylistcontent::where([['checklistid',$id], ['categoryid', $dataset=>['categoryid'] ]])
->update($dataset['isCheck']);
}
Would you be able to advise how I can use the array to get the 'checklistid' and 'categoryid' to be used as the where clause of the update statement and then the 'isCheck' to be set in the update.
You don't need dataset array, rather do the following:
foreach($checklistdata as $key =>$value){
categorylistcontent::where('checklistid',$id)->where('categoryid',$key)
->update(['isCheck'=>$value]);
}
You can't do that with just one query, but you could do that with two queries. An example:
$check = categorylistcontent::query();
$notCheck = categorylistcontent::query();
foreach ($request->checklist as $item) {
$query = $item['isCheck'] === 1 ? 'check' : 'notCheck';
$$query->orWhere(function($q) use($item) {
$q->where('checklistid', $item['checklistid'])->where('categoryid', $item['categoryid']);
}
}
$check->update(['check' => 1]);
$notCheck->update(['check' => 1]);
I haven't tested this exact code, but I think it will be helpful for you to get the idea.
You need to update multiple rows by putting it in foreach loop
eg:
foreach($checklistdata as $key =>$value){
$dataset[] = ['checklistid'=>$id,'categoryid' => $key,'isCheck'=>$value];
categorylistcontent::where([['checklistid',$id], ['categoryid', $dataset=>['categoryid'] ]])
->update($dataset['isCheck']);
}

Predicting future IDs used before saving to the DB

I am saving a complex dataset in Laravel 4.2 and I am looking for ways to improve this.
$bits has several $bobs. A single $bob can be one of several different classes. I am trying to duplicate a singular $bit and all its associated $bobs and save all of this to the DB with as few calls as possible.
$newBit = $this->replicate();
$newBit->save();
$bobsPivotData = [];
foreach ($this->bobs as $index => $bob) {
$newBob = $bob->replicate();
$newBobs[] = $newBob->toArray();
$bobsPivotData[] = [
'bit_id' => $newBit->id,
'bob_type' => get_class($newBob),
'bob_id' => $newBob->id,
'order' => $index
];
}
// I now want to save all the $bobs kept in $newBobs[]
DB::table('bobs')->insert($newBobs);
// Saving all the pivot data in one go
DB::table('bobs_pivot')->insert($bobsPivotData);
My problem is here, that I cant access $newBob->id before I have inserted the $newBob after the loop.
I am looking for how best to reduce saves to the DB. My best guess is that if I can predict the ids that are going to be used, I can do all of this in one loop. Is there a way I can predict these ids?
Or is there a better approach?
You could insert the bobs first and then use the generated ids to insert the bits. This isn't a great solution in a multi-user environment as there could be new bobs inserted in the interim which could mess things up, but it could suffice for your application.
$newBit = $this->replicate();
$newBit->save();
$bobsPivotData = [];
foreach ($this->bobs as $bob) {
$newBob = $bob->replicate();
$newBobs[] = $newBob->toArray();
}
$insertId = DB::table('bobs')->insertGetId($newBobs);
$insertedBobs = DB::table('bobs')->where('id', '>=', $insertId);
foreach($insertedBobs as $index => $newBob){
$bobsPivotData[] = [
'bit_id' => $newBit->id,
'bob_type' => get_class($newBob),
'bob_id' => $newBob->id,
'order' => $index
];
}
// Saving all the pivot data in one go
DB::table('bobs_pivot')->insert($bobsPivotData);
I have not tested this, so some pseudo-code to be expected.

Auth assignment database call yii2

I am using Yii2 and using the yii\rbac\DbManager for auth assignment.
I was looking at the logs to see where all the database calls are coming from and this query
SELECT `b`.* FROM `auth_assignment` `a`, `auth_item` `b` WHERE
((`a`.`item_name`=`b`.`name`) AND (`a`.`user_id`='91')) AND (`b`.`type`=1)
Keeps running again and again, sometimes 10/15 times in succession.
I have added
'authManager' => [
'class' => 'yii\rbac\DbManager',
'cache' => 'cache'
],
As the docs say that will cache the auth assignments (I am using Memcached). But it doesnt seem to work...
Anyone have any idea? Either how to cache it or why it keeps getting called so many times?
Cheers
Add caching in
vendor/yiisoft/yii2/rbac/DbManager.php
(Also in all places you need caching)
this code:
$all_data = $this->db->cache(function ($db) use ($query) {
return $query->all($db);
},360);
public function getAssignments($userId)
{
if (empty($userId)) {
return [];
}
$query = (new Query)
->from($this->assignmentTable)
->where(['user_id' => (string) $userId]);
$all_data = $this->db->cache(function ($db) use ($query) {
return $query->all($db);
},360);
$assignments = [];
foreach ($all_data as $row) {
$assignments[$row['item_name']] = new Assignment([
'userId' => $row['user_id'],
'roleName' => $row['item_name'],
'createdAt' => $row['created_at'],
]);
}
return $assignments;
}
https://github.com/yiisoft/yii2/issues/3168
Only cache auth_item, auth_rule and auth_item_child data. All these
data are cached as a single entry in cache. Note that
auth_assignment is too big to be cached (imagine a system with
millions of users).

How to filter a result returned by a function get_entries() of a stream entries driver in pyrocms?

I have a stream/table named profiles. All of its column are stream-fields. I am trying to restrict the result returned by the the function, get_entries() depending on some criteria. Below is my code:
$data = [
'stream' => 'profiles',
'namespace' => 'users',
'where' => 'user_id = 3' // lets say, this is my criteria
];
$row = $this->streams->entries->get_entries($data); // returns empty
The varaible, $row resulted in empty array. Although there is one row in table, profiles where user_id is 3. I have read the documentation of pyrocms and it pretty much says the exact way to use the where clause (just like above).
NOTE: I have also tried writing like
'where' => 'profiles.user_id = 3'`
joy !to avoid table conflict. Still no
But when I write the code like this:
$row = $this->streams->entries->get_entries($query);
$query = [
'stream' => 'profiles',
'namespace' => 'users'
];
// No where clause this time
$row = $this->streams->entries->get_entries($query);
This time $row returns all rows including the row with user id 3.
I am unable to use the where clause in get_entries in a right way. I might have done some mistake. Help me out guyz
NOTE: I am using community edition.
I think this might be due to a bug (well, not a bug, but a feature that doesn't work as intended).
If I'm intentionally issue a wrong query, the sql query output is
SELECT [ ... ] LEFT JOIN `default_profiles` as `profiles` ON `profiles`.`user_id`=`default_profiles`.`created_by` WHERE (user_id` = 1) ORDER BY `default_profiles`.`created` DESC
Here you see that PyroCMS tries to lookup the data for the "created_by" field. And that doesn't work in this case.
If you disable the 'created_by' field, you should get the correct row:
$this->streams->entries->get_entries(
array(
'stream' => 'profiles',
'namespace' => 'users',
'where' => 'user_id = 3',
'disable' => 'created_by'
)
);
It would be great if you could file an issue on the pyrocms github page. If you won't I'll do it in the next few days.
Model
public function get_entries($table, $where) {
$this->db->select('*');
$this->db->from($table);
foreach ($where as $key => $value) {
$this->db->where($key, $value);
}
$this->query = $this->db->get();
foreach ($this->query->result_array() as $row) {
$array1[] = $row;
}
if ($this->query->num_rows() == 0)
return false;
else
return $array1;
}
call this model function as
$row = $this->streams->entries->get_entries('profiles',array('user_id '=>3));

Categories