I want to update multiple rows in the database with codeigniters update_batch() function.
But the field specified in the where should also be changed.
The following code should make it clear:
$set = array(
array(
'token' => '65787131678754',
'device' => 'none',
'new_token_value' => ''
),
array(
'token' => '75798451315464',
'device' => 'none',
'new_token_value' => ''
)
);
$this->db->update_batch(TBL_NAME, $set, 'token');
Tokens specified in token should be updated with device to 'none' and the token itself should be set to empty string ''.
Is this possible with update_batch() function?
In sql I would write something like
UPDATE TBL_NAME
SET token='', device='none'
WHERE token='65787131678754'
for one update but this is not practicable for multiple, so I want to use the update_batch() function.
I created a helper function mostly identical to the codeigniter batch_update() function.
But with the ability to update the index itself. The new value is defined by index_update_key.
function update_batch($db, $table = '', $set = NULL, $index = NULL, $index_update_key = '') {
if ($table === '' || is_null($set) || is_null($index) || !is_array($set)) {
return FALSE;
}
$sql = 'UPDATE ' . $db->protect_identifiers($table) . ' SET ';
$ids = $when = array();
$cases = '';
//generate the WHEN statements from the set array
foreach ($set as $key => $val) {
$ids[] = $val[$index];
foreach (array_keys($val) as $field) {
if ($field != $index && $field != $index_update_key) {
$when[$field][] = 'WHEN ' . $db->protect_identifiers($index)
. ' = ' . $db->escape($val[$index]) . ' THEN ' . $db->escape($val[$field]);
} elseif ($field == $index) {
//if index should also be updated use the new value specified by index_update_key
$when[$field][] = 'WHEN ' . $db->protect_identifiers($index)
. ' = ' . $db->escape($val[$index]) . ' THEN ' . $db->escape($val[$index_update_key]);
}
}
}
//generate the case statements with the keys and values from the when array
foreach ($when as $k => $v) {
$cases .= "\n" . $db->protect_identifiers($k) . ' = CASE ' . "\n";
foreach ($v as $row) {
$cases .= $row . "\n";
}
$cases .= 'ELSE ' . $k . ' END, ';
}
$sql .= substr($cases, 0, -2) . "\n"; //remove the comma of the last case
$sql .= ' WHERE ' . $index . ' IN (' . implode(',', $ids) . ')';
return $db->query($sql);
}
Now I can do the following
$set = array(
array(
'token' => '657871316787544',
'device' => 'none',
'new_token_value' => ''
),
array(
'token' => '757984513154644',
'device' => 'none',
'new_token_value' => ''
)
);
update_batch($this->db, 'table_name', $set, 'token', 'new_token_value');
and the sql output is
UPDATE `b2c` SET
`token` = CASE
WHEN `token` = '657871316787544' THEN ''
WHEN `token` = '757984513154644' THEN ''
ELSE token END,
`device` = CASE
WHEN `token` = '657871316787544' THEN 'none'
WHEN `token` = '757984513154644' THEN 'none'
ELSE device END
WHERE token IN (657871316787544,757984513154644)
$this->db->where('option1', $option1);<br/>
$this->db->update_batch('table_name', $data, 'option2');
Related
I am developing a database-driven website using Laravel. Now I am having a problem with updating database. I want to run bulk update. Normally I run like this.
$data = [
[
'col_1'=>$value1
],
[
'col_1'=>$value2
],
[
'col_1'=>$value3
],
[
'col_1'=>$value4
]
];
MyTableObject::update($data)->where('col_2'=>$col_2_val);
As you can see in the above code, where clause is checking for only one condition for all rows that are to be updated. But what I want is that I want the different where clause condition for each row or query. To use foreach and run query for each row, it will be time-consuming because I have to update a lot of rows. To demonstrate it, it will be something like this.
$data = [
[
'col_1'=>$value1,
'col_2'=>$where_value_1 // This is not the column to be updated. That is for where clause.
],
[
'col_1'=>$value2,
'col_2'=>$where_value_2 // This is not the column to be updated. That is for where clause.
],
[
'col_1'=>$value3,
'col_2'=>$where_value_3
],
[
'col_1'=>$value4,
'col_2'=>$where_value_4
]
];
MyTableObject::update($data)->where('where_col_name'=>'col_2');
I found this link, but the answers are not clear and complete. Is it possible to do it in Laravel, and how can I do it?
If I'm understanding this correctly, col_1 is the value you wish to use in update and col_2 is the value you're querying against. Try the following (may need some adjustments)
collect($data)->each(function(array $row) {
MyTableObject::where('where_col_name', $row['col_2'])->update(['col_1' => $row['col_1']]);
});
Here is the batch update function which I wrote to use in my Laravel projects. Its first parameter is the table name string, second is the key name string on which you want to update the row or rows and mostly it will be the 'id', the third parameter is the data array in the format:
array(
array(
'id' => 1,
'col_1_name' => 'col_1_val',
'col_2_name' => 'col_2_val',
//.....
),
array(
'id' => 2,
'col_1_name' => 'col_1_val',
'col_2_name' => 'col_2_val',
//.....
),
//.....
);
Function:
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')
I have the following arrays and I would like to convert each one of them into individual strings. In other words, break the array into individual pieces.
$formatsArray = $_POST['formats'];
$topicsArray = $_POST['topics'];
This is because I would like to include the individual strings in the following query "
$resources = "select * from resources where
stage LIKE '%".$stage."%'
AND format LIKE '%".$formats."%'";
$run_query = mysqli_query($con, $resources);
This is because format expect an individual string for comparison, such as lets assume the array is ["video", "blogs", "articles"], it wouldn't work if format was to be compared with video,blogs,articles but rather video, blogs or articles.
I hope this is clear, and for any clarification, please advise.
All the best,
Update:
$formats = explode(',', $formatsArray);
$topics = explode(',', $topicsArray);
$resources = "select * from resources where
stage LIKE '%".$stage."%'
AND format LIKE '%".$formats."%' AND topic LIKE '%".$topics."%' ";
update:
$run_query = mysqli_query($con, $resources);
while($row = mysqli_fetch_array($run_query)) {
$data[] = array(
'format' => $row['format'],
'title' => $row['title'],
'costs' => $row['cost'],
'stage' => $row['stage'],
'topic' => $row['topic'],
'link' => $row['link']
);
}
Update
include('db.php');
$query = 'select * from resources where ';
$query .= 'stage LIKE :stage and';
$execute[':stage'] = '%' . $stage . '%';
if(!empty($_POST['formats'])){
foreach($_POST['formats'] as $key => $format) {
$query .= 'format LIKE :format' . $key . ' and ';
$execute[':format' . $key] = '%' . trim($format) . '%';
}
}
if(!empty($_POST['topics'])){
foreach($_POST['topics'] as $key => $topic) {
$query .= 'topic LIKE :topic' . $key . ' and ';
$execute[':topic' . $key] = '%' . trim($topic) . '%';
}
}
$query = rtrim($query, ' and ');
if(!empty($execute)) {
$stmt = $con->prepare($query);
$stmt->execute($execute);
} else {
echo 'You must search for something';
}
while($row = mysqli_fetch_array($query)) {
$data[] = array(
'format' => $row['format'],
'title' => $row['title'],
'costs' => $row['cost'],
'stage' => $row['stage'],
'topic' => $row['topic'],
'link' => $row['link']
);
}
Ignoring the necessity of prepared statements, you could do:
$formats = implode('","', $formatsArray);
$topics = implode('","', $topicsArray);
$resources = "select * from resources where
stage LIKE '%".$stage."%'
AND format IN(".$formats.") AND topic IN(\"".$topics."\") ";
By adding the " before and after each , when you implode each array, your array would become e.g.
video","blogs","articles
So, we need to add the " to the beginning and end of each IN list. This will make the final query like:
select * from resources where
stage LIKE '%".$stage."%'
AND format IN("video","blogs","articles") AND ...
I think this would do it. This also will resolve the injection hole by using prepared statements.
$query = 'select * from resources where ';
if(!empty($_POST['formats'])){
foreach($_POST['formats'] as $key => $format) {
$query .= 'stage LIKE :stage' . $key . ' or ';
$execute[':stage' . $key] = '%' . trim($format) . '%';
}
}
if(!empty($_POST['topics'])){
foreach($_POST['topics'] as $key => $topic) {
$query .= 'topic LIKE :topic' . $key . ' or ';
$execute[':topic' . $key] = '%' . trim($topic) . '%';
}
}
$query = rtrim($query, ' or ');
if(!empty($execute)) {
echo $query;
print_r($execute);
//$stmt = $mysqli->prepare($query);
//$stmt->execute($execute);
} else {
echo 'You must search for something';
}
Gives you a query of
select * from resources where stage LIKE :stage0 or stage LIKE :stage1 or topic LIKE :topic0 or topic LIKE :topic1 or topic LIKE :topic2 or topic LIKE :topic3
and bound values of:
Array
(
[:stage0] => %test%
[:stage1] => %test1%
[:topic0] => %value1%
[:topic1] => %value2%
[:topic2] => %value3%
[:topic3] => %value4%
)
Here's the initial code I had for when I thought the data was paired..
foreach($formats as $key => $format) {
$topic = $topics[$key];
$query .= '(stage LIKE :stage' . $key . ' and topic LIKE :topic' . $key . ') or ';
$execute[':stage' . $key] = '%' . trim($format) . '%';
$execute[':topic' . $key] = '%' . trim($topic) . '%';
}
A few links on prepared statements:
http://php.net/manual/en/mysqli.quickstart.prepared-statements.php
http://php.net/manual/en/mysqli.prepare.php
http://php.net/manual/en/mysqli-stmt.execute.php
I am trying to use an array to output a form with the following function:
public function createArrayForm($table, $do, $formDesc = '', $id, $array, $markFields = false) {
if (!isset($table) && !isset($do)) {
self::show_error('One or more parameters are missing in ' . __FUNCTION__);
} elseif ($table == 'update' && !isset($id)) {
self::show_error('For this form to be built, and ID must be set. Missing parameter `ID` in ' . __FUNCTION__);
}
if (is_array($array) && $do == 'insert') {
$out .= '<form action="' . $_SERVER['PHP_SELF'] . '?id=' . $id . '&table=' . $table . '" method="post" class="form-horizontal" ' . $formAppend . '>';
$out .= '<div class="form-desc">' . $formDesc . '</div>';
$out .= $markFields ? '<h3>Input Fields</h3>' : '';
foreach ($array as $type => $fieldname) {
if ($type == 'input') {
$out .= generateInputField($fieldname);
}
}
$out .= $markFields ? '<h3>Content Fields</h3>' : '';
foreach ($array as $type => $fieldname) {
if ($type == 'textarea') {
$out .= generateTextarea($fieldname, $cke);
}
}
$out .= $markFields ? '<h3>Images Fields</h3>' : '';
foreach ($array as $type => $fieldname) {
if ($type == 'image') {
$out .= generateImgField($fieldname);
}
}
$out .= form_hidden('user_data', '1');
$out .= form_hidden('id', self::generateID());
$out .= form_close();
return $out;
}
And call:
$arr = array("textarea"=>"project_name", "input"=>"created", "input"=>"last_modified", "input"=>"published");
echo $automate->createArrayForm('projects', 'insert', 'Some form desc', '123', $arr, true);
But it only outputs:
When it should look something like this:
Only one of each, for example input, is returned. Rather than all instances of it. So "input"=>"created", "input"=>"last_modified", "input"=>"published" should make three inputs, but it only returns one.
You're re-using array keys. So
$arr = array("textarea"=>"project_name", "input"=>"created", "input"=>"last_modified", "input"=>"published");
will end up looking like this:
$arr = array("textarea"=>"project_name", "input"=>"published");
Instead, modify your code to something like this:
$arr = array("textarea"=>array("project_name"), "input"=>array("created", "last_modified", "published"));
Then take those individual arrays, and iterate through them.
foreach ($array['input'] as $fieldname) { // etc and so on
In PHP, you cannot have arrays that share keys.
You'll be best creating a simple array, and creating sub-entries, so that you maintain order, but can have more than one input/textarea.
Like so:
$arr = array(
array('type' => 'textarea', 'name' => 'project_name'),
array('type' => 'input', 'name' => 'created'),
array('type' => 'input', 'name' => 'published'),
array('type' => 'input', 'name' => 'last_modified')
)
This would also allow you to add more parameters than type/name.
The problem is that you use the same key for everything in the array.
A possibility would be to exchange values and keys
$arr = array("textarea"=>"project_name", "input"=>"created", "input"=>"last_modified", "input"=>"published");
[...]
foreach ($array as $fieldname => $type) {
if ($type == 'textarea') {
$out .= generateTextarea($fieldname, $cke);
}
}
[...]
I have this array:
Array
(
[users] => Array
(
[0] => Array
(
[column] => email
[value] => fskdjhfkjf#mai.com
)
[1] => Array
(
[column] => nickname
[value] => dpucci
)
)
[social] => Array
(
[0] => Array
(
[column] => user_id
[value] => opweirpor
)
)
)
Starting from this array I'll have to build a string that will look like this:
insert(users, array(email=>fskdjhfkjf#mai.com,nickname=>dpucci)
And another like this:
insert(social, array(user_id=>opweirpor)
This is what I am doing:
foreach ($tables as $table => $queries) {
foreach ($queries as $query) {
$insert .= $query['column'] . '=>' . $query['value'] . ',';
}
echo 'insert(' . $table . ', array(' . $insert . ')';
}
The problem is that the result I am getting is the following:
insert(users, array(email=>fskdjhfkjf#mai.com,nickname=>dpucci)
and:
insert(social, array(email=>fskdjhfkjf#mai.com, nickname=>dpucci, user_id=>opweirpor)
This is because the variable $insert is incremented each new loop and it is adding ALL the results instead that only the ones I need for each $tables loop.
How can I achieve the expected result?
Because you keep appending your string to the same $insert in the inner loop, which is ran many times. Just clear the insert variable after you have finished with it, i.e. after your inner loop. (Untested)
foreach ($tables as $table => $queries) {
foreach ($queries as $query) {
$insert .= $query['column'] . '=>' . $query['value'] . ',';
}
echo 'insert(' . $table . ', array(' . $insert . ')';
// add this line
$insert = "";
}
or, put it before the inner foreach loop, which has an advantage to ensure $insert is not polluted with previous codes or otherwise uninitialized giving PHP warnings.
foreach ($tables as $table => $queries) {
// add this line
$insert = "";
foreach ($queries as $query) {
$insert .= $query['column'] . '=>' . $query['value'] . ',';
}
echo 'insert(' . $table . ', array(' . $insert . ')';
}
However, your code actually creates
insert(users, array(email=>fskdjhfkjf#mai.com,nickname=>dpucci,)
Note the comma after dupcci. Which I don't think is what you want. To fix this, simply remove the trailing comma with substr:
foreach ($tables as $table => $queries) {
// add this line
$insert = "";
foreach ($queries as $query) {
$insert .= $query['column'] . '=>' . $query['value'] . ',';
}
// add one more line here
$insert = substr($insert, 0, -1);
echo 'insert(' . $table . ', array(' . $insert . ')';
}
Also, check your desired output. It seems that the brackets are not balanced and the strings are unquoted. Are you sure it is what you want?
Reset the variable $insert.
foreach ($tables as $table => $queries) {
$insert = '';
foreach ($queries as $query) {
$insert .= $query['column'] . '=>' . $query['value'] . ',';
}
echo 'insert(' . $table . ', array(' . $insert . ')';
}
Is there any way to make the below-given code reusable for different tables, e.g. using foreach? So, how to send and use arrays of column names and values? Any example is highly appreciated.
<?php
include_once 'include/DatabaseConnector.php';
if(isset($_POST['flightNum'])) {
$flightNum=$_POST['flightNum'];
$from=$_POST['from'];
$STADate=$_POST['STADate'];
$query = 'UPDATE flightschedule
SET frm="'.$from.'",STADate="'.$STADate.'"
WHERE flightNum="'.$flightNum.'"';
DatabaseConnector::ExecuteQuery($query);
echo '1';
} else {
echo '0';
}
?>
UPDATE: What if I don't know column names apriori? How to create flexible UPDATE statement?
you can convert your code into a reusable function. for example.
function updateDB($tableName, $flightNum, $from, $STADate) {
include_once 'include/DatabaseConnector.php';
$query = 'UPDATE ' . $tableName
SET frm="'.$from.'",STADate="'.$STADate.'"
WHERE flightNum="'.$flightNum.'"';
$execute = DatabaseConnector::ExecuteQuery($query);
return $execute;
}
and to use it
if(isset($_POST['flightNum']) {
$update = updateDB('flightschedule', $_POST['flightNum'], $_POST['from'], $_POST['STADate']);
echo $update;
}
Update:
I want to send an array of column names to the function 'updateDB'.
Let's say these are column names for SET and WHERE parts of UPDATE
statement. And then I could use FOREACH, but I need some example for
this.
this is how you can do it.
function updateDB($tableName, $columns, $where) {
//do some validation here to check if proper data is being passed like
if(!isarray($columns)) {
throw new Exception('argument two $columns should be an associative array');
}
include_once 'include/DatabaseConnector.php';
$query = 'UPDATE ' . $tableName;
foreach($columns as $column => $data) {
$query .= ' SET ' . $column . ' = ' . $data . ', ';
}
//remove last comma from the update query string
$query = substr($query, 0, -1);
$query .= ' WHERE ' . $where['column'] . ' = ' . $where['value'];
$execute = DatabaseConnector::ExecuteQuery($query);
return $execute;
}
and to use it.
if(isset($_POST['flightNum']) {
$columns = array(
'frm' => $_POST['frm'],
'STADate' => $_POST['STADate']
);
$where = array(
'column'=> 'flightNum',
'value' => $_POST['flightNum']
);
$update = updateDB('flightschedule', $columns, $where);
echo $update;
}
Something like this should work:
function generateUpdateQuery($table, $fields, $where) {
$updateFields = array();
foreach ($fields as $k => $v) {
$updateFields[] = $k.' = \''.$v.'\'';
}
$sqlQuery = 'UPDATE '.$table.
' SET '.implode(', ', $updateFields).
' WHERE '.implode(' AND ', $where);
return $sqlQuery;
}
echo generateUpdateQuery('table_name',
array(
'field_a' => '10',
'field_b' => 'hello',
'field_c' => 'world'),
array(
'id=12',
'datetime > NOW()')
);
Keep in mind that this is a simple example without any security check. Something like PDO would be recommended.
Moreover, if you're looking for something more robust and flexible you should give a look to a ORM system, like Doctrine