Codeigniter - Batch Update with Multiple Where Conditions - codeigniter
For starters, the Codeigniter documentation on update_batch does not exist. kenjis was kind enough to provide some documentation and submit it to the repository. Hopefully they pull it soon.
Does anyone know how to add multiple where conditions to Codeigniters update_batch command?
My Desired Use:
$where = array(
'title',
'name'
);
$this->db->update_batch('mytable', $data, $where);
When I tried this code I got the follow error:
A Database Error Occurred
One or more rows submitted for batch updating is missing the specified index.
Filename: C:\wamp\www\wheel\system\database\DB_active_rec.php
Line Number: 1451
Update Batch Documentation by kenjis:
$this->db->update_batch();
Generates an update string based on the data you supply, and runs the query. You can either pass an array or an object to the function. Here is an example using an array:
$data = array(
array(
'title' => 'My title' ,
'name' => 'My Name 2' ,
'date' => 'My date 2'
),
array(
'title' => 'Another title' ,
'name' => 'Another Name 2' ,
'date' => 'Another date 2'
)
);
$this->db->update_batch('mytable', $data, 'title');
// Produces:
// UPDATE `mytable` SET `name` = CASE
// WHEN `title` = 'My title' THEN 'My Name 2'
// WHEN `title` = 'Another title' THEN 'Another Name 2'
// ELSE `name` END,
// `date` = CASE
// WHEN `title` = 'My title' THEN 'My date 2'
// WHEN `title` = 'Another title' THEN 'Another date 2'
// ELSE `date` END
// WHERE `title` IN ('My title','Another title')
The first parameter will contain the table name, the second is an associative array of values, the third parameter is the where key.
Sources:
kenjis's documentation update:
https://bitbucket.org/kenjis/ci-user-guide/changeset/3d579dd14afe
saintnicster's pull request: https://github.com/EllisLab/CodeIgniter/pull/448
You can't add multiple where clauses to update_batch(). It only accepts a string as the third parameter for the where clause so I'm sure there's no way to do this the way the method is currently written.
From the source:
/**
* Update_Batch
*
* Compiles an update string and runs the query
*
* #param string the table to retrieve the results from
* #param array an associative array of update values
* #param string the where key
* #return object
*/
public function update_batch($table = '', $set = NULL, $index = NULL)
I am using codeigniter 3.1.5 and had the same problem, but I solved my problem as follows:
$data = array(
array(
'title' => 'My title' ,
'name' => 'My Name 2' ,
'date' => 'My date 2'
),
array(
'title' => 'Another title' ,
'name' => 'Another Name 2' ,
'date' => 'Another date 2'
)
);
$this->db->where('name','My Name 2');
$this->db->update_batch('mytable', $data, 'title');
Produces it:
// Produces:
// UPDATE `mytable`
// SET `name` = CASE
// WHEN `title` = 'Another title' THEN 'Another Name 2'
// WHEN `title` = 'My title' THEN 'My Name 2'
// ELSE `name`
// END,
// `date` = CASE
// WHEN `title` = 'My title' THEN 'My date 2'
// WHEN `title` = 'Another title' THEN 'Another date 2'
// ELSE `date`
// END
// WHERE `title` IN ('My title','Another title')
// AND `name` = 'My Name 2'
UPDATE
I had a problem trying to add more than 100 records with update_batch, for example:
$data = [1=>a,2=>b ... 200=>zz];
First call (with WHERE):
// Produces:
// UPDATE `mytable`
// SET `name` = CASE
// WHEN `title` = 'My title' THEN 'My Name 2'
// WHEN `title` = 'Another title' THEN 'Another Name 2'
// ELSE `name`
// END,
// `date` = CASE
// WHEN `title` = 'My title' THEN 'My date 2'
// WHEN `title` = 'Another title' THEN 'Another date 2'
// ELSE `date`
// END
// WHERE `title` IN ('My title','Another title')
// AND `name` = 'My Name 2'
Second call on (Without WHERE):
// Produces:
// UPDATE `mytable`
// SET `name` = CASE
// WHEN `title` = 'My title' THEN 'My Name 2'
// WHEN `title` = 'Another title' THEN 'Another Name 2'
// ELSE `name`
// END,
// `date` = CASE
// WHEN `title` = 'My title' THEN 'My date 2'
// WHEN `title` = 'Another title' THEN 'Another date 2'
// ELSE `date`
// END
// WHERE `title` IN ('My title','Another title')
Try this:
$chunk1 = array_chunk($data,100);
for($i=0;$i < count($chunk1);$i++) {
$this->upload_model->update_data($chunk1[$i],'My Name 2');
}
Model:
public function update_data($data='',$name=''){
$this->db->where('name',$name);
$this->db->update_batch('mytable', $data, 'title');
}
Multiple where conditions are broken in update_batch because the WHERE query is being cleared in the batch loop.
Here is the batch update loop:
for ($i = 0, $total = count($this->qb_set_ub); $i < $total; $i += $batch_size)
{
if ($this->query($this->_update_batch($this->protect_identifiers($table, TRUE, NULL, FALSE), array_slice($this->qb_set_ub, $i, $batch_size), $index)))
{
$affected_rows += $this->affected_rows();
}
$this->qb_where = array();
}
Notice that the passed WHERE conditions are cleared by $this->qb_where = array();.
In CodeIgniter v3.1.10, the offending line is on 1940 in DB_query_builder.php. This produces a very unexpected behavior where WHERE conditions work for the first batch processed (default 100) and fail for subsequent batches.
There are two possible solutions:
Use the 4th batch_size parameter of update_batch and pass a large number such as 100,000 so all the queries are processed in the first batch and the WHERE condition is not cleared.
Update the offending line to restore the initial WHERE conditions.
Code for Solution #2:
// Save initial where conditions.
$where_holder = $this->qb_where;
// Batch this baby
$affected_rows = 0;
for ($i = 0, $total = count($this->qb_set_ub); $i < $total; $i += $batch_size)
{
if ($this->query($this->_update_batch($this->protect_identifiers($table, TRUE, NULL, FALSE), array_slice($this->qb_set_ub, $i, $batch_size), $index)))
{
$affected_rows += $this->affected_rows();
}
// Restore intial where conditions.
$this->qb_where = $where_holder;
}
Hope this helped!
Try this one also !
Suppon you have import data through csv/excel
then store all record in single array like:
Array
(
[0] => Array
(
[price] => 100.00
[part_no] => PD001
[brand] => 44
[special_price] => 90.10
)
[1] => Array
(
[price] => 200.00
[part_no] => PD002
[special_price] => 150.00
)
)
Step 2:
Call to model
$model = new CatalogModel();
$result = $model->batchUpdateData($final_array);
function batchUpdateData($array = array()){
$query = $this->db->table('product');
$price = array();
$part_no = array();
$special = array();
$brand = array();
if (!empty($array)) {
foreach ($array as $key => $value) {
$price[] = $value['price'];
$part_no[] = $value['part_no'];
$special[] = $value['special_price'];
$brand[] = $value['brand'];
}
$num = count($part_no);
$sql ="UPDATE product SET ";
// price colum update
$sql .=" price = CASE ";
for($i=0; $i < $num; $i++){
$sql .=" WHEN part_no = '".$part_no[$i]."' AND brand = '".$brand[$i]."' THEN '".$price[$i]."' ";
}
$sql .="ELSE price END ,";
// special colum update
$sql .=" special_price = CASE ";
for($i=0; $i < $num; $i++){
$sql .=" WHEN part_no = '".$part_no[$i]."' AND brand = '".$brand[$i]."' THEN '".$special[$i]."' ";
}
$sql .="ELSE special_price END";
$sql .=" WHERE part_no IN ('" . implode("','", $part_no) . "') ";
return $this->db->query($sql);
}
}
This will product query like:
UPDATE `product` SET `price` = CASE WHEN `part_no` = 'PD001' and `brand` = '44' THEN '100.00' WHEN `part_no` = 'PD002' and `brand` = '44' THEN '200.00' ELSE `price` END
WHERE `part_no` IN('PD001','PD002');
If this helpfull give a thumbs up
Related
Laravel insert or update multiple rows
Im new in laravel, and im trying to update my navigation tree. So i want to update my whole tree in one query without foreach. array( array('id'=>1, 'name'=>'some navigation point', 'parent'='0'), array('id'=>2, 'name'=>'some navigation point', 'parent'='1'), array('id'=>3, 'name'=>'some navigation point', 'parent'='1') ); I just want to ask - is there posibility in laravel to insert(if new in array) or update my current rows in database? I want to update all, because i have fields _lft, _right, parent_id in my tree and im using some dragable js plugin to set my navigation structure - and now i want to save it. I tried to use Navigation::updateOrCreate(array(array('id' => '3'), array('id'=>'4')), array(array('name' => 'test11'), array('name' => 'test22'))); But it works just for single row, not multiple like i tried to do. Maybe there is another way to do it?
It's now available in Laravel >= 8.x The method's first argument consists of the values to insert or update, while the second argument lists the column(s) that uniquely identify records within the associated table. The method's third and final argument is an array of columns that should be updated if a matching record already exists in the database: Flight::upsert([ ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150] ], ['departure', 'destination'], ['price']);
I wonder why this kind of feature is not yet available in Laravel core (till today). Check out this gist The result of the query string would look like this: here I am putting the code here just in case the link breaks in the future, I am not the author: /** * Mass (bulk) insert or update on duplicate for Laravel 4/5 * * insertOrUpdate([ * ['id'=>1,'value'=>10], * ['id'=>2,'value'=>60] * ]); * * * #param array $rows */ function insertOrUpdate(array $rows){ $table = \DB::getTablePrefix().with(new self)->getTable(); $first = reset($rows); $columns = implode( ',', array_map( function( $value ) { return "$value"; } , array_keys($first) ) ); $values = implode( ',', array_map( function( $row ) { return '('.implode( ',', array_map( function( $value ) { return '"'.str_replace('"', '""', $value).'"'; } , $row ) ).')'; } , $rows ) ); $updates = implode( ',', array_map( function( $value ) { return "$value = VALUES($value)"; } , array_keys($first) ) ); $sql = "INSERT INTO {$table}({$columns}) VALUES {$values} ON DUPLICATE KEY UPDATE {$updates}"; return \DB::statement( $sql ); } So you can safely have your arrays inserted or updated as: insertOrUpdate( array( array('id'=>1, 'name'=>'some navigation point', 'parent'='0'), array('id'=>2, 'name'=>'some navigation point', 'parent'='1'), array('id'=>3, 'name'=>'some navigation point', 'parent'='1') ) ); Just in case any trouble with the first line in the function you can simply add a table name as a second argument, then comment out the line i.e: function insertOrUpdate(array $rows, $table){ ..... } insertOrUpdate(myarrays,'MyTableName'); NB: Be careful though to sanitise your input! and remember the timestamp fields are not touched. you can do that by adding manually to each arrays in the main array.
I've created an UPSERT package for all databases: https://github.com/staudenmeir/laravel-upsert DB::table('navigation')->upsert( [ ['id' => 1, 'name' => 'some navigation point', 'parent' => '0'], ['id' => 2, 'name' => 'some navigation point', 'parent' => '1'], ['id' => 3, 'name' => 'some navigation point', 'parent' => '1'], ], 'id' );
Eloquent Style public function meta(){ // in parent models. return $this->hasMany('App\Models\DB_CHILD', 'fk_id','local_fk_id'); } . . . $parent= PARENT_DB::findOrFail($id); $metaData= []; foreach ($meta['meta'] as $metaKey => $metaValue) { if ($parent->meta()->where([['meta_key', '=',$metaKey]] )->exists()) { $parent->meta()->where([['meta_key', '=',$metaKey]])->update(['meta_value' => $metaValue]); }else{ $metaData[] = [ 'FK_ID'=>$fkId, 'meta_key'=>$metaKey, 'meta_value'=> $metaValue ]; } } $Member->meta()->insert($metaData);
No, you can't do this. You can insert() multiple rows at once and you can update() multiple rows using same where() condition, but if you want to use updateOrCreate(), you'll need to use foreach() loop.
I didn't find a way to bulk insert or update in one query. But I have managed with only 3 queries. I have one table name shipping_costs. Here I want to update the shipping cost against the shipping area. I have only 5 columns in this table id, area_id, cost, created_at, updated_at. // first get ids from table $exist_ids = DB::table('shipping_costs')->pluck('area_id')->toArray(); // get requested ids $requested_ids = $request->get('area_ids'); // get updatable ids $updatable_ids = array_values(array_intersect($exist_ids, $requested_ids)); // get insertable ids $insertable_ids = array_values(array_diff($requested_ids, $exist_ids)); // prepare data for insert $data = collect(); foreach ($insertable_ids as $id) { $data->push([ 'area_id' => $id, 'cost' => $request->get('cost'), 'created_at' => now(), 'updated_at' => now() ]); } DB::table('shipping_costs')->insert($data->toArray()); // prepare for update DB::table('shipping_costs') ->whereIn('area_id', $updatable_ids) ->update([ 'cost' => $request->get('cost'), 'updated_at' => now() ]);
in your controller use DB; public function arrDta(){ $up_or_create_data=array( array('id'=>2, 'name'=>'test11'), array('id'=>4, 'name'=>'test22') ); var_dump($up_or_create_data); echo "fjsdhg"; foreach ($up_or_create_data as $key => $value) { echo "key ".$key; echo "<br>"; echo " id: ".$up_or_create_data[$key]["id"]; echo "<br>"; echo " Name: ".$up_or_create_data[$key]["name"]; if (Navigation::where('id', '=',$up_or_create_data[$key]["id"])->exists()) { DB::table('your_table_ name')->where('id',$up_or_create_data[$key]["id"])->update(['name' => $up_or_create_data[$key]["name"]]); }else{ DB::insert('insert into your_table_name (id, name) values (?, ?)', [$up_or_create_data[$key]["id"], $up_or_create_data[$key]["name"]]); } }
Empty POST string inserting as zero in MySQL even though column is set to NULL
I have a problem where my database column is set to NULL `max_vol` MEDIUMINT UNSIGNED NULL, and my $_POST['max_vol[]'] is an empty string ('') right up to the point of inserting or updating the row in the database (tested output with print_r()). It then inserts 0 (zero) instead of NULL. If I explicitly set max_vol to NULL it then works. $value['max_vol'] = empty($value['max_vol']) ? NULL : $value['max_vol']; but why does this happen? I thought setting an empty string to MySQL (with NULL set) inserted NULL. Here is my original code. Is this something CodeIgniter's query builder changes? $position_form_data = array(); // positions form data store // process the form data into arrays for database operations foreach( $_POST as $post_key=>$post_value ) { // ignore non-array post variables if( is_array( $post_value ) ) { foreach( $post_value as $form_key=>$form_value ) { if (!isset($position_form_data[$form_key])) { $position_form_data[$form_key] = array(); } $position_form_data[$form_key][$post_key] = $form_value; } } } // if id exists db->update else db->insert foreach($position_form_data as $value){ // $value['max_vol'] = empty($value['max_vol']) ? NULL : $value['max_vol']; // data for insert and replace db operations $data = array( 'id' => $value['id'], 'day' => $_POST['day'], 'title' => $value['title'], 'description' => $value['description'], 'max_vol' => $value['max_vol'], 'is_draft' => $is_draft, 'project_id' => $_POST['project_id'] ); //print_r($data);exit(); if( empty($value['id']) ) { $this->db->insert('positions', $data); } else { $this->db->replace('positions', $data); } Thanks.
I can't see any mistake, I can only recommend you try this: if( ! isset($value['max_vol'])){ if( is_null($value['max_vol'])){ $_max_vol = NULL; } else{ $_max_vol = NULL } } else{ $_max_vol = $value['max_vol']; } $data = array( 'id' => $value['id'], 'day' => $_POST['day'], 'title' => $value['title'], 'description' => $value['description'], 'max_vol' => $_max_vol, 'is_draft' => $is_draft, 'project_id' => $_POST['project_id'] );
Update multiple Rows in Codeigniter
I have been looking around but I have not found an answer yet. Kindly help if you know the answer. How do you update multiple rows in CI? In my MySQL: I have column names: ID, Settings Name, Settings Value ( Settings Name is Unique ) I have the ff Data: ID = 1, Settings Name = "Hello" , Settings Value = "True" ID = 2, Settings Name = "World", Settings Value = "Good" and more ... I also have a form that gets the Settings Value but I am not sure how to update it on the DB. How to update the True for the Hello being the Settings Name and update the Good for the World. I heard about insert_batch() but is there an update_batch()?
Are you using Active record? Yes there is an update batch: $this->db->update_batch(); $data = array( array( 'ID' => 1 , 'Settings Name' => 'Hello' , 'Settings Value' => 'True' ), array( 'ID' => '2' , 'Settings Name' => 'World' , 'Settings Value' => 'Good' ) ); $this->db->update_batch('mytable', $data, 'where_key'); From the documentation: The first parameter will contain the table name, the second is an associative array of values, the third parameter is the where key.
There is indeed an update_batch() method available in CodeIgniter already. You can use it your example like so: $data = array( array( 'ID' => 1, 'Settings Name' => 'Hello', 'Settings Value' => 'True' ), array( 'ID' => 2, 'Settings Name' => 'World', 'Settings Value' => 'Good' ) ); $this->db->update_batch('tableName', $data, 'id'); So what you have is an array of arrays, the children basically hold the data for each row in the database. The first parameter for update_batch() is the name of the database table, the second is the $data variable and the third is the column you want to use in the WHEN clause.
Here is a simple php code for perform this operations. <?php function basic_update($uwi='') { $this->load->database(); $data = array( 'ID' => 1, 'Settings Name' => 'Hello', 'Settings Value' => 'True' ); $this->db->where('ID', '1'); $this->db->update('<table name>', $data); $data1 = array( 'ID' => 2, 'Settings Name' => 'World', 'Settings Value' => 'Good' ); $this->db->where('ID', '2'); $this->db->update('<table name>', $data1); } In $this->db->where('ID', '1'); ID is your table field and 1 is the value. In array first parameter will contain the table name, the second parameter is an associative array of values
Codeigniter - Sending a query result_arry() to a View for form_dropdown
I am using form_dropdown() and have a a problem below: The form code is: echo form_dropdown($level,$level_options,'1'); It works when I use $level_options = array( '1' => 'Grade 6', '2' => 'Grade 7' ); but not when I send a $data['levels'] from controller to view For reference, the model database retrieve code is: public function getAllLevelNames() { $query = $this->db->query("SELECT level_description from levels ORDER BY level_description"); return $query->result_array(); } The Problem The problem is I get a dropdown pick list with: 0 Grade 6 1 Grade 7 The indexes are greyed out. How do I get rid of the indexes? Thanks in advance! P.S. I seem to have the form working now with a data['levels'] sent to the view. Now, the following code in my view seems to return "null" to my controller. Any ideas why please? $level = array( 'name' => 'level', 'id' => 'level', 'value' => '1', 'maxlength' => '50', 'size' => '50', 'style' => 'width:50%', ); $level_options = $levels; echo "<p>Level: "; echo form_dropdown($level,$level_options,'1'); Thanks!
You'll need to loop through your results_array and create a new array which is formatted correctly. $query = $this->db->query("SELECT level_description from levels ORDER BY level_description"); $for_dropdown = array(); foreach ($query->result_array() as $row) { $for_dropdown[$row->level_description] = $row->level_description; } return $for_dropdown; Also I'm not sure how your levels table is structured, but usually you'll have an ID of some sort, which will be the primary key. If you do have that, you can include it in your query and have something like this instead: $query = $this->db->query("SELECT id, level_description from levels ORDER BY level_description"); ... // other code $for_dropdown[$row->id] = $row->level_description;
Cakephp: Validation of elements in an array
I have a form where a student to enter subjects using a drop down list. The dropdown list takes subjects from a table. I want to validate this dropdown so that a subject is only selected once by the student. The subjects are being looped. How can i do this? My controller for ($i = 1; $i < sizeof($this->data['ApplicantOlevelQualification']['olevel_subject_code']); $i++) { if ($this->data['ApplicantOlevelQualification']['olevel_subject_code'][$i] != "") { $this->ApplicantOlevelQualification->create(); $this->ApplicantOlevelQualification->id = null; $this->ApplicantOlevelQualification->set(array( 'applicants_detail_id' => $app_id, 'olevel_subject_code' => $this->data['ApplicantOlevelQualification']['olevel_subject_code'][$i], 'grade' => $this->data['ApplicantOlevelQualification']['grade'][$i], 'mark' => $this->data['ApplicantOlevelQualification']['mark'][$i], 'centre_number'=> $centre_number, 'candidate_number'=> $candidate_number, 'exam_body_code'=> $exam_body_code, 'year_written'=> $year_written, ) ); My add.ctp echo "<tr><td>" . $this->Form->label('Subject: '); echo "</td><td>"; echo $this->Form->select("ApplicantOlevelQualification.olevel_subject_code.$s",$mySubjects); echo "</td><td>"; Model 'olevel_subject_code' => array( 'numeric' => array( 'rule' => array('valids'), ), ),
This can be unique by adding GROUP BY on dropdown list $subjects = $this->ApplicantOlevelQualification->OlevelSubject->find('list',array('fields'=>array('code','name'),'group'=>array(code)));