Is there a MySQL function to 'use existing value' during update? - php

Is there a way with MySQL to specify "use previous / inherit / no change / existing value"?
Rather than needing to pull the current data from the database and use it, or have a customized database function excluding editing those columns.
if(x > y) {
$role_id = 3;
} else {
$role_id = '#no-change'; // Is there a way to do this? (not proper SQL syntax)
}
$update_user = $this->db->update('users',
array(
'first_name' => filterName($post['first_name']),
'last_name' => filterName($post['last_name']),
'email' => filterEmail($post['email']),
'role_id' => $role_id,
), $user_id_to_edit, 'user_id');
In a case like this where the db function is using prepared statements (not shown) I can't use the column name as to reflect the current value.
Is there such a MySQL function / variable that will essentially "ignore" updating that column? (just leave the existing value)
UPDATE: Here's the Update function:
public function update($table, $data, $where_id, $column = 'user_id') {
// Check for $table or $data not set
if (( empty( $table ) || empty( $data )) || empty($data) ) {
return false;
}
// Initiate variable to append to
$placeholders ='';
// Parse data for column and placeholder names
foreach ($data as $key => $value) {
$placeholders .= sprintf('%s=:%s,', $key, $key);
}
// Trim excess commas
$placeholders = rtrim($placeholders, ',');
// Append where ID to $data
$data['where_id'] = $where_id;
// Prepary our query for binding
$stmt = $this->db->prepare("UPDATE {$table} SET {$placeholders} WHERE $column = :where_id");
// Execute the query
$stmt->execute($data);
// Check for successful insertion
if ( $stmt->rowCount() ) {
return true;
}
return false;
}

You could try this:
$data = array(
'first_name' => filterName($post['first_name']),
'last_name' => filterName($post['last_name']),
'email' => filterEmail($post['email']))
if(x > y) {
$data['role_id'] = 3;
}
$update_user = $this->db->update('users', $data, $user_id_to_edit, 'user_id');
That way, you can customize $data before hand if you like.
I should also mention, if you're concerned about redundancy, you can put your data sanitation inside a function. Something along the lines of:
function sanitize($data) {
if(x > y) {
$data['role_id'] = 3;
} else {
if(isset($data['role_id']) {
unset($data['role_id']);
}
}
return $data;
}
$data = array(
'first_name' => filterName($post['first_name']),
'last_name' => filterName($post['last_name']),
'email' => filterEmail($post['email']))
$update_user = $this->db->update('users', sanitize($data), $user_id_to_edit, 'user_id');
Edit: Something I should mention is that, if we're talking pure SQL, all you need to do is omit those columns from the query, so:
UPDATE table SET Col1=val1, Col2=val2, Col3=val3 WHERE id=val
But if for some reason you don't want to update Col3, just remove it from the query:
UPDATE table SET Col1=val1, Col2=val2 WHERE id=val
Since you have a function that just adds whatever you give it, you just need to sanitize the data ahead of time. That's probably the best way to do it.

Related

zend update is deleting old values

I am doing update in zend which in some cases doesn't update all the fields, the fields that are not updated become null as if we are doing an add.
This is the code from the Controller
$result = $theuserModel->updateUserTest(
$id,
$this->getRequest()->getPost('user_name'),
/*some code*/
$this->getRequest()->getPost('user_postee')
);
if ($result) {
$this->view->notif = "Successfull Update";
return $this->_forward('index');
}
The corresponding model
public function updateUserRest($id, $nom,$poste)
{
$data = array(
'user_name' => $nom,
'user_postee' => $poste
);
$result=$this->update($data, 'user_id = '. (int)$id);
return $result;
}
I do an update for user_name only I found that the old value of user_postee got deleted and replaced by the default value (initial value which we get at the time of creation) for example null.
Thanks in advance!
I have done this changes (bad solution) If anyone has another one optimised
->Controller
if($this->getRequest()->getPost('user_name')){
$resultname=$userModel->updateUserName($id,$this-
>getRequest()->getPost('user_name'));
}
if($this->getRequest()->getPost('user_postee')){
$resultpostee=$userModel->updateUserPoste($id,$this-
>getRequest()->getPost('user_postee'));
}
if ($resultname|| $resultpostee){
$this->view->notif = "Mise à jour effectuée";
return $this->_forward('index');
}
-> Model
public function updateUserName($id, $name)
{
$data = array(
'user_name' => $name
);
$result=$this->update($data, 'user_id = '. (int)$id);
return $result;
}
public function updateUserPostee($id, $postee)
{
$data = array(
'user_postee' => $poste
);
$result=$this->update($data, 'user_id = '. (int)$id);
return $result;
}
that is complete correct response of update in Zend Db Table.
I believe your assumption is if the value of 'user_postee' is null then it should not be updated into the database, am I correct.
The answer is they will update the new value of "NULL" into the database.
To avoid it , what you should do is
using fetchrow() to get the value of the line by id
foreach user_name and user_postee check if the value of them matching the array value your fetched , if nothing changed or Null, then use the old value from array , if new value exist use new value insert into the array , finally use update to update the new array into database
Assume your Table Column is also "user_name" and "user_postee"
public function updateUserRest($id, $nom,$poste)
{
$row = $this->fetchRow('user_id = '. (int)$id);
if(!empty($nom) && $row['user_name'] != trim($nom)){
$row['user_name'] = $nom;
}
if(!empty($poste) && $row['user_poste'] != trim($poste)){
$row['user_poste'] = $poste;
}
$result=$this->update($row, 'user_id = '. (int)$id);
return $result;
}

Insert Batch php mysql with check duplicate and sum values of field duplicate

i have 1 million data using foreach.
ex table:
table
the data
data
i want to inserting that's data using batch/multipleinsert, but i have problem when i got duplicate data. if data duplicate i want the field amount will sum and update amount field with sum amount duplicated data.
this is my code before
<?php
foreach ($data_transaksi as $key)
{
if($key['STATUS_COA'] != $key['CHART_OF_ACCOUNT_STATUS'])
{
if($key['ACCOUNT_CATEGORY_CODE'] == '9')
{
$amount = round($key['AMOUNT'],2);
}
else
{
$amount = round($key['AMOUNT'],2) *-1;
}
}
else
{
if($key['ACCOUNT_CATEGORY_CODE'] == '9')
{
$amount = round($key['AMOUNT'],2)*-1;
}
else
{
$amount = round($key['AMOUNT'],2);
}
}
$dt_exsis = $this->ledger_model->cek_data_coa_exsis($key['COA_CODE'],$modul,$ID_USER);
if(empty($dt_exsis['id']))
{
//TRYINSERTINGBATCH
// $datainsert[] = '('.$key['COA_CODE'].','.$amount.','.$ID_USER.',"'.$modul.'")';
// $test = $key['COA_CODE'];
$datainput = array(
'COA_CODE' => $key['COA_CODE'],
'AMOUNT' => $amount,
'MODUL' => $modul,
'USER_ID' => $ID_USER
);
$this->ledger_model->save_rows_to_table($datainput,'finance_lapkue_temp');
}
else
{
$amount_fix = $amount + $dt_exsis['AMOUNT'];
$data=array(
'AMOUNT' => $amount_fix
);
$this->ledger_model->edit_rows_to_table_where($data,'finance_lapkue_temp','id',$dt_exsis['id']);
// $q = "UPDATE finance_lapkue_temp set AMOUNT = '$amount_fix' where id = '".$dt_exsis['id']."'";
// $this->db->query($q);
}
// $data_amount[$key['COA_CODE']] += $amount;
}
?>
if i using this code, the proccess so slow
Good option will to pass only data to DB that you want to insert. All the data cleaning task can be done in controller.
// Create a data array and add all info
$data = [];
//if user does not exist add it in array
if (empty($dt_exist($id))) {
$data[$ID_USER] = array(
'COA_CODE' => $key['COA_CODE'],
'AMOUNT' => $amount,
'MODUL' => $modul,
'USER_ID' => $ID_USER
);
}
else {
//if user exist in $data just modify the amount
if (!empty($data[$ID_USER])) {
$data[$ID_USER]['AMOUNT'] += $dt_exsis['AMOUNT'];
}
else {
// if user does not exist in data create add all info
$data[$dt_exsis['ID_USER']] = array(
'COA_CODE' => $dt_exsis['COA_CODE'],
'AMOUNT' => $dt_exsis['amount'],
'MODUL' => $dt_exsis['modul'],
'USER_ID' => $dt_exsis['ID_USER']
);
}
}
This will save multiple calls to DB and at the end you can pass $data and do multiple insert.

Prepare data for inserting ST_GeomFromText in database (Codeigniter + MySql)

I'm having a problem inserting my data in database with Codeigniter. I have this testing seed function
$latitude = rand(45.500000 * 1000000, 46.400000 * 1000000) / 1000000;
$longitude = rand(13.600000 * 1000000, 15.500000 * 1000000) / 1000000;
$data = array(
'unique_id' => '12319334',
'latitude' => $latitude,
'longitude' => $longitude,
'coordinates' => "ST_GeomFromText('POINT($latitude $longitude)')",
);
$locationId = $this->Locations->insert($data);
And this is insert function in model
function insert($data, $tableName = "")
{
if ($tableName == "") {
$tableName = $this->table;
}
$this->db->insert($tableName, $data);
return $this->db->insert_id();
}
And this is a query that happens
INSERT
INTO
`locations`(
`unique_id`,
`latitude`,
`longitude`,
`coordinates`
)
VALUES(
'ZTE1NGY2YT',
45.990292,
14.948462,
'ST_GeomFromText(\'POINT(45.582315 14.821478)\')'
)
Error i get is Cannot get geometry object from data you send to the GEOMETRY field
After some testing in phpmyadmin i figure out that query for inserting this kind of data should look like this
INSERT
INTO
`locations`(
`unique_id`,
`latitude`,
`longitude`,
`coordinates`
)
VALUES(
'ZTE1NGY2YT',
45.990292,
14.948462,
ST_GeomFromText('POINT(45.582315 14.821478)')
)
So somehow i need to get rid off single quotes (') in 'ST_GeomFromText(\'POINT(45.582315 14.821478)\')' line
Anybody got idea how to properly prepare data (without executing direct query, since there is a lot more data to store) so it can be process properly?
If you need any additional information please let me know and i will provide.
Thank you!
Actually this did it
$this->db->set('coordinates', "ST_GeomFromText('POINT($latitude $longitude)')", false);
So i made a small hack on my model (i know its not pretty but it works for now) and data is inserted..
function insert($data, $tableName = "")
{
foreach ($data as $key => $value) {
if ($key == 'coordinates') {
$this->db->set('coordinates', $value, false);
unset($data['coordinates']);
}
}
if ($tableName == "") {
$tableName = $this->table;
}
$this->db->insert($tableName, $data);
return $this->db->insert_id();
}
Unfortunately, CodeIgniter's database escape logic assumes that the pattern is field_name => rendered data. This means that you can't use things like NOW() or MD5(), or ST_GeomFromText. You can see the logic here:
foreach ($data as $key => $val)
{
$fields[] = $this->escape_identifiers($key);
$values[] = $this->escape($val);
}
The best option I've managed is to extend the DB driver and have the model call a custom function. The other option is to drop back into raw SQL and manually escape the values.

Eloquent Query Dynamically based on json

I want to query dynamically based on payload(json) from database.
Example:
$data = [{"key":"age","relation":">","value":"15"},{"operator":"OR"},{"key":"age","relation":"<=","value":"20"}]
I want to do query based on that payload.
Right now what I'm doing is:
$query = User::all();
$payload = json_decode($data, true);
foreach($payload as $value){
if ($value['key'] == 'age') {
$query = $query->where('birthday', $value['relation'], Carbon::now()->subYears($value['age'])->format('Y-m-d');)
}
if($value['key'] == 'gender'{
$query = $query->where('gender', $value['relation'], $value['gender']);
}
}
The problem is yes it can work, but I don't think this is best approach. I don't get any solution to use the "operator" key. Operator usage is to change where to orWhere.
Any solution or tips to make it call dynamically like this?. I want my column at DB neat and simple. I can only think this way.
Thanks!
Encountering this problem, I would go with Local Query Scopes. In this approach You create a model method named scopeJson() or whatever you feel better with to handl all conditions inside. I tried to handle most conditions here not only single where and orWhere. I assumed that your payload contains only one builder at a time.
public function scopeJson($query, $json)
{
$wheres = [
'between' => ['whereBetween', 'not' => 'whereNotBetween'],
'null' => ['whereNull', 'not' => 'whereNotNull'],
'or' => ['orWhere', 'not' => 'orWhereNot'],
'in' => ['whereIn', 'not' => 'whereNotIn'],
'and' => ['where', 'not' => 'orWhereNot'],
'raw' => 'whereRaw'
];
$builder = json_decode($json);
if (count($builder) > 0) {
$query->where(
$builder[0]->key,
$builder[0]->relation,
$builder[0]->value
);
// notBetween, notNull, notOr, notIn, notAnd
if (stripos($builder[1]->operator, 'not') !== false) {
$whereCondition = $wheres[strtolower(substr($builder[1]->operator, 3))]['not'];
} else {
$whereCondition = $wheres[strtolower($builder[1]->operator)];
}
if (count($builder[2]) == 3) {
if ($whereCondition == 'whereRaw') {
$query->$whereCondition(implode(" ", $builder[2]));
} else {
// where, whereNot
$query->$whereCondition(
$builder[2]->key,
$builder[2]->relation,
$builder[2]->value
);
}
} elseif (count($builder[2]) == 2) {
// whereBetween, whereNotBetween, where, whereNot
$query->$whereCondition(
$builder[2]->key,
$builder[2]->value
);
} elseif (count($builder[2]) == 1) {
// whereNull, whereNotNull, whereRaw
$query->$whereCondition(
$builder[2]->key ?? $builder[2]->value // PHP 7.0 Null Coalescing Operator
);
}
}
return $query;
}
If this method is defined within your User's model then you can use it this way:
$users = User::json($data)->get();
PS: Although it should work, I didn't test it.
You can do raw query like this.
$data = '[{"key":"age","relation":">","value":"15"},{"operator":"OR"},{"key":"age","relation":"<=","value":"20"}]';
$query = "SELECT * FROM tablename WHERE";
$payload = json_decode($data, true);
foreach ($payload as $value) {
if (isset($value['operator'])) {
$query .= " " . $value['operator'];
} else {
if ($value['key'] == 'age') {
$query .= " 'birthday' " . $value['relation'] . " " . Carbon::now()->subYears($value['value'])->format('Y-m-d');
}
if ($value['key'] == 'gender') {
$query .= " 'gender' " . $value['relation'] . " " . $value['gender'];
}
}
}
This results in a query like this :
SELECT * FROM tablename WHERE 'birthday' > 2001-07-02 OR 'birthday' <= 1996-07-02
Of course, you might use printf() for formatting and making this cleaner in some other way but this will get you started hopefully.
You can use a variable function name to add your orWhere logic:
$query = User::all();
$payload = json_decode($data, true);
$function = 'where';
foreach($payload as $value){
if(isset($value['operator'])){
$function = $value['operator'] == 'OR' ? 'orWhere' : 'where';
} else {
if ($value['key'] == 'age') {
$query = $query->$function('birthday', $value['relation'], Carbon::now()->subYears($value['age'])->format('Y-m-d');)
} else {
$query = $query->$function($value['key'], $value['relation'], $value['value']);
}
}
}
As long as your json data doesn't match your database (ie. the json has age but the database has birthday) you will not be able to avoid having that if/else statement. That custom logic will have to remain.
Ultimately this idea is its own limiter because the stored queries will have to represent the current state of the database. This means that the maintenance cost of these queries will be significant the moment data is stored in a different way -- if you changed the column birthday to date_of birth all of your stored queries will break. Avoid this.
Instead this goal is better achieved by storing the queries on the Model using Query Scopes and Relationships. If you still need a dynamic list of requests you can store keywords that are associated with the queries and loop through them.

Class returns integer instead of object while trying to get column data

Just getting into OOP, and changing from mysql queries to PDO. I am trying to create a class that will return the column names and meta data for a table. This is so I can output copy/paste data for all the tables I use. I have used such a tool, based on the mysql extension, for ages and it spits out variants such as complete SELECT/INSERT/UPDATE queries. Amongst other things I now want to add DECLARE listings for Stored Procedures - so getting meta data like type and length are essential. With about 150 tables across two schemas, such automation is essential.
With uncertainty about the reliability of getColumnMeta I hunted for code and found what looked good in a Sitepoint answer. I have attempted to wrap it in a class and mimic its original context but I am simply getting a number 1 when I try to echo or print_r the response. I have also had 'not an object' error messages while trying solutions.
This is the calling code
$db_host="localhost";
$db_username='root';
$db_pass='';
$db_name='mydatabase';
try{
$db= new PDO('mysql:host='.$db_host.';dbname='.$db_name,$db_username,$db_pass, array(PDO::ATTR_PERSISTENT=>false));
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
}
catch(PDOException $e){echo "Error: ".$e->getMessage()."<br />"; die(); }
include 'ColMetaData.php'; //the file containing the class for getting a column listing for each table
$coldat= new supplyColumnMeta($db);
$tablemet=$coldat->getColumnMeta('groups'); // a manual insertion of a table name for testing
echo $tablemet;
And this is the class that sits in the include file
class supplyColumnMeta{
public function __construct($db){
$this->db=$db;
}
/**
* Automatically get column metadata
*/
public function getColumnMeta($table)
{$this->tableName=$table;
// Clear any previous column/field info
$this->_fields = array();
$this->_fieldMeta = array();
$this->_primaryKey = NULL;
// Automatically retrieve column information if column info not specified
if(count($this->_fields) == 0 || count($this->_fieldMeta) == 0)
{
// Fetch all columns and store in $this->fields
$columns = $this->db->query("SHOW COLUMNS FROM " . $this->tableName, PDO::FETCH_ASSOC);
foreach($columns as $key => $col)
{
// Insert into fields array
$colname = $col['Field'];
$this->_fields[$colname] = $col;
if($col['Key'] == "PRI" && empty($this->_primaryKey)) {
$this->_primaryKey = $colname;
}
// Set field types
$colType = $this->parseColumnType($col['Type']);
$this->_fieldMeta[$colname] = $colType;
}
}
return true;
}
protected function parseColumnType($colType)
{
$colInfo = array();
$colParts = explode(" ", $colType);
if($fparen = strpos($colParts[0], "("))
{
$colInfo['type'] = substr($colParts[0], 0, $fparen);
$colInfo['pdoType'] = '';
$colInfo['length'] = str_replace(")", "", substr($colParts[0], $fparen+1));
$colInfo['attributes'] = isset($colParts[1]) ? $colParts[1] : NULL;
}
else
{
$colInfo['type'] = $colParts[0];
}
// PDO Bind types
$pdoType = '';
foreach($this->_pdoBindTypes as $pKey => $pType)
{
if(strpos(' '.strtolower($colInfo['type']).' ', $pKey)) {
$colInfo['pdoType'] = $pType;
break;
} else {
$colInfo['pdoType'] = PDO::PARAM_STR;
}
}
return $colInfo;
}
/**
* Will attempt to bind columns with datatypes based on parts of the column type name
* Any part of the name below will be picked up and converted unless otherwise sepcified
* Example: 'VARCHAR' columns have 'CHAR' in them, so 'char' => PDO::PARAM_STR will convert
* all columns of that type to be bound as PDO::PARAM_STR
* If there is no specification for a column type, column will be bound as PDO::PARAM_STR
*/
protected $_pdoBindTypes = array(
'char' => PDO::PARAM_STR,
'int' => PDO::PARAM_INT,
'bool' => PDO::PARAM_BOOL,
'date' => PDO::PARAM_STR,
'time' => PDO::PARAM_INT,
'text' => PDO::PARAM_STR,
'blob' => PDO::PARAM_LOB,
'binary' => PDO::PARAM_LOB
);
}
Here's the problem:
public function getColumnMeta($table)
{
$this->tableName=$table;
// Clear any previous column/field info
$this->_fields = array();
$this->_fieldMeta = array();
$this->_primaryKey = NULL;
// Automatically retrieve column information if column info not specified
if(count($this->_fields) == 0 || count($this->_fieldMeta) == 0)
{
// Fetch all columns and store in $this->fields
$columns = $this->db->query("SHOW COLUMNS FROM " . $this->tableName, PDO::FETCH_ASSOC);
foreach($columns as $key => $col)
{
// Insert into fields array
$colname = $col['Field'];
$this->_fields[$colname] = $col;
if($col['Key'] == "PRI" && empty($this->_primaryKey)) {
$this->_primaryKey = $colname;
}
// Set field types
$colType = $this->parseColumnType($col['Type']);
$this->_fieldMeta[$colname] = $colType;
}
}
return true;//<<--- not returning an object/array!
}
Your getColumnMeta method returns a boolean, true. The string representation of this value is, of course, 1. If you want this method to return all of the meta-data, change the return statement to something like:
return array(
'fields' => $this->_fields,
'meta' => $this->_fieldMeta,
'primary' => $this->_primaryKey
);
There are some other issues with your code, too, but seeing as this is not codereview.stackexchange, I'm not going to go into too much details. I will say this, though: Please, try to follow the coding standards that most major players adhere to: these standards can be found here: PHP-FIG.
Oh, and if you want to show the meta-data, don't echo them, but rather var_dump or print_r them, seeing as you return an array or an object.
Or at least echo json_encode($instance->getColumnMeta($table)); to get a correct string representation of the returned value(s).

Categories