I am making a transaction table with an auto increment field of BIGINT(20).
When a new transaction is added, the insert Id is retrieved and formatted to be more readable:
public function add_transaction($paymethod, $cursus_id)
{
$this->load->model('Config_model');
$btw = $this->Config_model->get('transactions.btw');
$query = " INSERT INTO transacties(userid, paymethod, amount, btw_pc)
VALUES((SELECT userid FROM users WHERE lcase(username)=lcase('{$this->session->userdata('username')}')),
'{$paymethod}',
(SELECT prijs FROM p_cursus_uitvoering WHERE uitvoering_id = {$cursus_id}),
{$btw});";
$this->db->query($query);
$insertId = $this->db->insert_id();
$newCode = date('Ymd') . str_pad($insertId, 8, '0', STR_PAD_LEFT);
$this->db->where('transact_id', $insertId);
$this->db->update('transacties', ['transact_id' => $newCode]);
return $newCode;
}
The result is that the ID get updated from eg: 5 to 2015041800000005.
This is working perfectly, but as you can see, the newCode is returned in the function and used by another function where it's reinserted in another table.
Here is where the problem arises, the ID turns into: 201504192058506757.
Even when I echo the newCode, it still prints 201504192058506757.. even though it is inserted correctly once, but incorrectly the second time!
EDIT:
Here is the code snippet in which the function is called:
public function workshop(){
$this->load->model('Inschrijven_model');
$paymethod = $this->input->post("paymethod");
//eigenlijk uitvoering_id.....
$cursus_id = $this->input->post("cursus_id");
if($paymethod == null || $cursus_id == null){
redirect('cursus');
}
if($this->Inschrijven_model->cursus_has_room($cursus_id)){
if(!$this->Inschrijven_model->cursus_ingeschreven($cursus_id)){
$this->load->model('Cc_payment_model');
$this->load->model('Cursus_model');
$amount = $this->Cursus_model->get_price($cursus_id);
$orderId = $this->Cc_payment_model->add_transaction($paymethod, $cursus_id);
$this->Inschrijven_model->cursus_inschrijven($cursus_id,$orderId);
$function = explode('_',$paymethod);
$this->{$function[0]}($function[1], $amount, $orderId);
}else{
echo "Al ingeschreven";
}
}else{
echo "geen ruimte";
}
}
And here is the code snippet in which the return $newCode is being reinserted:
public function cursus_inschrijven($cursus_id,$transaction_id){
$query = " INSERT INTO p_cursus_in(uitvoering_id, userid, transact_id)
VALUES({$cursus_id},(SELECT userid FROM users WHERE lcase(username)=lcase('{$this->session->userdata('username')}')),{$transaction_id})";
$this->db->query($query);
}
It is reinserted so I can make a connection between someone's registration into a class and their payment for that class.
You have to convert the BIGINT to string before you get it into PHP
Here's how you can convert it with a SQL statement:
https://dev.mysql.com/doc/refman/5.7/en/cast-functions.html#function_convert
I don't know what framework you're using but you have to do a conversion if you're using BIGINT whose length is not supported by PHP directly. It seems that the framework will read the record after updated to check if the operation's done successfully, and because of the process of reading without the conversion, you got a wrong number.
You can also use PHP GMP module to handle BIGINT:
https://php.net/manual/en/book.gmp.php
Related
I am working on an application built with codeigniter that used to register students. It was working fine till date but yesterday multiple students try to register at same instance, then data was conflicted and entered wrong data into database.
what should I do to prevent this. I have used following code.
public function sregister()
{
if (!$_SERVER['HTTP_REFERER']) redirect(base_url(), 'refresh');
$query1 = $this->db->query("SELECT MAX(right(ref,3)) AS `maxid` FROM p2 WHERE ref LIKE '" . date('Ymd') . "%'")->row()->maxid;
$p2data['name'] = $this->input->post('pname');
$p2data['fname'] = $this->input->post('pfname');
$p2data['mname'] = $this->input->post('pmname');
$p2data['dob'] = date('m/d/Y', strtotime($this->input->post('pdob')));
$p2data['category'] = $this->input->post('pcategory');
$p2data['gender'] = $this->input->post('pgender');
$p2data['nationality'] = $this->input->post('pnationality');
$p2data['marital'] = $this->input->post('pmarital');
$p2data['ref'] = date('Ymd') . sprintf('%03d', ($query1 + 1));
$this->db->insert('p2', $p2data);
}
You can use commit and roll back statements in codeigniter to prevent this.
Begin a transaction and when the status is true, commit the changes or else roll back to initial. This prevents any inconsistency in the database.
Example:
$this->db->trans_begin();
$this->db->query('QUERY1');
$this->db->query('QUERY2');
$this->db->query('QUERY3');
if ($this->db->trans_status() === FALSE){
$this->db->trans_rollback();
}else{
$this->db->trans_commit();
}
Or you can refer :https://codeigniter.com/user_guide/database/transactions.html
This is a classic (and fortunately easily avoidable, even without transactions) race condition.
Instead of determining by yourself what the next ID must be, you should have some sort of auto-increment do the hard work for you.
If the ref field you're calculating is the main index for the table, just run an alter on the table and set it to auto-increment. Then, do not insert anything on that field, just fill the others, like this:
public function sregister()
{
if (!$_SERVER['HTTP_REFERER']) redirect(base_url(), 'refresh');
$query1 = $this->db->query("SELECT MAX(right(ref,3)) AS `maxid` FROM p2 WHERE ref LIKE '" . date('Ymd') . "%'")->row()->maxid;
$p2data['name'] = $this->input->post('pname');
$p2data['fname'] = $this->input->post('pfname');
$p2data['mname'] = $this->input->post('pmname');
$p2data['dob'] = date('m/d/Y', strtotime($this->input->post('pdob')));
$p2data['category'] = $this->input->post('pcategory');
$p2data['gender'] = $this->input->post('pgender');
$p2data['nationality'] = $this->input->post('pnationality');
$p2data['marital'] = $this->input->post('pmarital');
$this->db->insert('p2', $p2data);
return $this->db->insert_id();
}
a successful insert will return the Insertion ID, which is nothing more than the value of the table's auto-increment field for the row you just inserted.
I have a php class
<?php
class Students
{
public $sesscode;
public $db_data;
function __construct($sesscode)
{
$this->sesscode = $sesscode;
$this->db_data = dbfetch(null, "SELECT * FROM students WHERE code = ?", [$this->sesscode]); //this simply returns data from db
$this->db_data = (empty($this->db_data)) ? $this->createProfile() : $this->db_data[0];
}
public function createProfile()
{
dbquery(null, "INSERT INTO students (code) VALUES (?)", [$this->sesscode]);
return dbfetch(null, "SELECT * FROM students WHERE code = ?", [$this->sesscode])[0];
}
public function updateAccount($row, $key, $val)
{
$data = json_decode($this->db_data[$row], true);
if (!$data) {
$data = [
$key => $val,
];
} else {
$data[$key] = trim($val);
}
dbquery(null, "UPDATE students SET account = ? WHERE code = ?", [json_encode($data), $this->sesscode]);
}
public function getData($row)
{
return json_decode($this->db_data[$row], true);
}
}
?>
Usage:
$student = new Student($sesscode); //sesscode is just some string
if (!empty($_POST)){
$student->updateAccount("account", "course", $_POST['course']);
//see class code/updateAccount method above for parameters. This means update dB table row "account", decode json and set course to post value of course
$student->updateAccount("account", "email", $_POST['email']);
$student->updateAccount("account", "phone", $_POST['phone']);
$student->updateAccount("account", "institution", $_POST['institution']);
}
My problem is, only the last line executes (and no, it isn't overwriting the data)
Moving the line that sets phone to take the place of institution will set phone and ignore those above it.
The line
$data = json_decode($this->db_data[$row], true);
resets the value of $data every time your updateAccount account function runs. It resets it to the value you fetched from the database when you created the object. Therefore this doesn't take account of any changes you've made in previous calls to updateAccount. So in fact it does overwrite your data, but probably just quite not in the way you imagined.
You need to either:
a) keep $data in-memory as a (private) property of the class rather than resetting it each time updateAccount runs,
or
b) re-fetch the existing data from the database each time updateAccount runs,
or
c) Store your data in relational format (with fields in separate columns) instead of as JSON within a single column - meaning you could update individual fields without interfering with the others,
and/or
d) provide a version of updateAccount where multiple values can be updated simultaneously - running separate UPDATE queries for each field is very inefficient.
Recently I have been creating a function to record every changes in the database, simply I record it to a table.
Table backup_log: backupTableName, backupLastUpdate, backupLastupload
So, every time changed, its will record the date to "backup_log".
It's runs well, but I have problem when I need Last Insert Id, I using $orderid = $dbh->lastInsertId('orderId'); to get "last insert Id", but its return "0".
I figure out that the problem comes when I put querylog() function that runs another query while the query() function still also run. coz its using same PDO object.
So, any suggestion for me to able to get "last insert id", while runs querylog() ?
This is my code:
//$dbh is object of PDO connection
$sql = query("INSER INTO orders ...");
$orderid = $dbh->lastInsertId('orderId'); // table "orders"
function query($sql){
#this function is not the full version, just for example
global $dbh;
$query=$dbh->exec($sql);
querylog($sql); // Log every insert, delete and update query
}
function querylog($sql){
global $dbh; // PDO object
global $now;
//Table backup_log: backupTable, backupLastUpdate, backupLastupload
###### Log: for insert and update ########
#if the query too long, cut for 200 chars and convert them to array
$sql_clean_space=str_replace(array(" ",'`'),array(" ",''),$sql); //change double space to single space
if(strlen($sql_clean_space) > 200){
$sql_clean_space = substr($sql_clean_space, 0, 200); // get only 200 char
}
$sql_array=explode(" ", $sql_clean_space); //convert to array to get the command name, insert or update
$now_date=$now;
#save log
if(strtolower($sql_array[0])=='insert' or strtolower($sql_array[0])=='delete'){
#check whether the query is insert or update
$sqlx = "SELECT * FROM backup_log WHERE backupTable='".$sql_array[2]."'";
$query=$dbh->query($sqlx);
$check_row=$query->rowCount($query);
if($check_row){
$sql_log="UPDATE backup_log SET backupLastUpdate='$now' WHERE backupTable='". $sql_array[2] . "'";
}else{
$sql_log="INSERT INTO backup_log VALUES('".$sql_array[2]."', '$now_date','$now_date')";
}
}elseif(strtolower($sql_array[0])=='update'){
$sqlx = "SELECT * FROM backup_log WHERE backupTable='".$sql_array[1]."'";
$query=$dbh->query($sqlx);
$check_row=$query->rowCount($query);
if($check_row){
$sql_log="UPDATE backup_log SET backupLastUpdate='$now' WHERE backupTable='". $sql_array[1] . "'";
}else{
$sql_log="INSERT INTO backup_log VALUES('".$sql_array[1]."', '$now_date','$now_date')";
}
}
$query_log=$dbh->exec($sql_log);
####### End of log ######################
}
Maybe you can assign last insert id in query functions and it can be preserved while query executions. See my code below;
$sql = query("INSER INTO orders ...", 'orderId'); //$sql is equal to last insert id
$orderid = $sql
function query($sql, $specific_id_column){
#this function is not the full version, just for example
global $dbh;
$query=$dbh->exec($sql);
$last_id = $dbh->lastInsertId($specific_id_column);
querylog($sql); // Log every insert, delete and update query
return $last_id;
}
I am trying to make a database of Users. One user can have an indefinite number of phone numbers. So in the form I’ve created a js function that will give me new input fields and they put the information into a nestled array.
I am doing a double foreach loop to go through my array, and add SQL queries to it based on if the id already exists and just needs to be updated or if it's entirely new and needs to be inserted. I add these SQL queries to a variable $phoneSql . When I echo that variable, it does contain a valid SQL query which works if I try it directly in phpMyAdmin.
This is the foreach loop code:
$phoneSql = 'SELECT id FROM user WHERE id = '.$id.' INTO #id;';
foreach($_POST['phone'] as $key => $value) {
foreach($_POST['user'][$key] as $id => $number) {
if($id == 0 && !$number == ''){
$phoneSql .= 'INSERT INTO phone_number (id, user_id, number) VALUES (NULL, #id, "'.$number.'");';
} else if (!$number == '') {
$phoneSql .= 'UPDATE phone_numbers SET user_id = #id, number = "'.$number.'" WHERE id = '.$id.';';
}
}
}
I have one edit.php page with the form, which posts to update.php where I have the foreach loop from above and following code:
$db->updatePhoneNumber($phoneSql);
It also gets the $id from the user I’m editing at the moment. Then it gets sent to db.php and into this function:
public function updatePhoneNumbers($phoneSql) {
$ phoneSql = $ phoneSql;
$sth = $this->dbh->prepare($phoneSql);
$sth->execute();
if ($sth->execute()) {
return true;
} else {
return false;
}
}
But this is not working. Can I add a variable with sql queries into a function like that or do I have to do it some other way? I’m quite new to this so I’m not sure how to proceed. I’ve tried searching for a solution but haven’t found any. I’m thankful for any advice.
What you should be doing is using an INSERT ... ON DUPLICATE KEY UPDATE ... construct, saving you a lot of that logic.
e.g.
INSERT INTO phone_number (id, user_id, number) VALUES (...)
ON DUPLICATE KEY UPDATE user_id=VALUES(user_id), number=VALUES(number)
With this, no need to select, test, then insert/update. You just insert, and MySQL will transparently convert it into an update if a duplicate key error occurs.
Below is part of a PHP database class someone else wrote, I have removed about 80% of it's code, all the un-related code to my question has been removed and just the amount remains that allows me to test this class without actually hitting a real database.
This class has a couple methods that let you set a key and value it then turns it into a mysql UPDATE and INSERT sql query using an array. I am trying to figure out how to use this code 100% so I can use this feature of it for UPDATE and INSERTS in my own application.
Basicly from what I gather you do something like this...
// assign some key/values to insert into DB
$db->assign('name', 'dfgd');
$db->assign('age', 87);
$db->assign('sex', 'female');
$db->assign('user_id', 4556);
// Do the insert
$db->insert('testing2');
Now where I am confused is I can keep on running code like this over and over on the page and it always will use the correct set of key/value array sets. Above you can see I used the assign() method 4 times and then call the insert() method which creates this
INSERT INTO test (name, age, sex, user_id) VALUES (jason davis, 26, male, 5345)
Now if I run another set like this on the same page...
// assign some key/values to insert into DB
$db->assign('name', 'dfgd');
$db->assign('age', 87);
$db->assign('sex', 'female');
$db->assign('user_id', 4556);
// Do the insert
$db->insert('testing2');
It then creates this...
INSERT INTO testing2 (name, age, sex, user_id) VALUES (dfgd, 87, female, 4556)
So how does it not combine the 2 sets of 4, so instead of inserting 8 record on the second insert, it completey replaces the first set of 4 values with the new set. This is great and what I want but I do not understand how it is happening? Also can this be improved anyway?
Below is a full class and my demo code, it can be ran without needing to connect to mysql for this demo, it will print to screen the SQL that it builds.
Also where would the public function reset() in the code below need to be used at, or would it not be needed?
<?php
class DB{
public $fields;
public function assign($field, $value){
$this->fields[$field] = ($value)==""?("'".$value."'"):$value;
}
public function assign_str($field, $value){
$this->fields[$field] = "'".addslashes($value)."'";
}
public function reset(){
$this->fields = array();
}
public function insert($table){
$f = "";
$v = "";
reset($this->fields);
foreach($this->fields as $field=>$value){
$f.= ($f!=""?", ":"").$field;
$v.= ($v!=""?", ":"").$value;
}
$sql = "INSERT INTO ".$table." (".$f.") VALUES (".$v.")";
//print SQL to screen for testing
echo $sql;
//$this->query($sql);
return $this->insert_id();
}
public function update($table, $where){
$f = "";
reset($this->fields);
foreach($this->fields as $field=>$value){
$f.= ($f!=""?", ":"").$field." = ".$value;
}
$sql = "UPDATE ".$table." SET ".$f." ".$where;
echo $sql;
//$this->query($sql);
}
public function query($_query){
$this->query = $_query;
$this->result = #mysql_query($_query, $this->link_id) or die( $_query."<p>".mysql_error($this->link_id) );
return $this->result;
}
public function insert_id(){
return #mysql_insert_id($this->link_id);
}
}
// start new DB object
$db = new DB;
// assign some key/values to insert into DB
$db->assign('name', 'jason davis');
$db->assign('age', 26);
$db->assign('sex', 'male');
$db->assign('user_id', 5345);
// Do the insert
$db->insert('test');
echo '<hr />';
// assign some key/values to insert into DB
$db->assign('name', 'dfgd');
$db->assign('age', 87);
$db->assign('sex', 'female');
$db->assign('user_id', 4556);
// Do the insert
$db->insert('testing2');
echo '<hr />';
// assign some key/values to UPDATE the DB
$db->assign('name', 'jason davis');
$db->assign('age', 26);
$db->assign('sex', 'male');
$db->assign('user_id', 5345);
// DO the DB UPDATE
$db->update('blogs', 'WHERE user_id = 23');
?>
Key in associative arrays are unique; assigning a new value erases the old.
If you still open for another database abstaction library, I want to suggest you to use AdoDB. It's can connect to multiple database, so you code will stay the same if you decide to switch database later. It have build in feature to sanitize data before insert/update.
For your code above, when you use AdoDB, you will write it like this:
$adodb =& ADONewConnection($dsn);
$data['name'] = 'dfgd';
$data['age'] = 87;
$data['sex'] = 'female';
$data['user_id'] = 4556;
// Do the insert
$result = $adodb->AutoExecute($table_name, $data, 'INSERT');
//If update, must have one of the key, such as id column
$result = $adodb->AutoExecute($table_name, $data, 'UPDATE', "id=$id");
You can read the documentation from the site, or inside zip file that you can download. I always use this library in all my project, even I prefer it more that build in CodeIgniter database library.
insert() and update() should (originally) set the $this->fields property back to an empty array upon execution, but you somehow (wrongly) deleted that code?
Update your code to this:
public function insert($table){
$f = "";
$v = "";
foreach($this->fields as $field=>$value){
$f.= ($f!=""?", ":"").$field;
$v.= ($v!=""?", ":"").$value;
}
$sql = "INSERT INTO ".$table." (".$f.") VALUES (".$v.")";
$this->reset();
//print SQL to screen for testing
echo $sql;
//$this->query($sql);
return $this->insert_id();
}
public function update($table, $where){
$f = "";
foreach($this->fields as $field=>$value){
$f.= ($f!=""?", ":"").$field." = ".$value;
}
$sql = "UPDATE ".$table." SET ".$f." ".$where;
$this->reset();
echo $sql;
//$this->query($sql);
}
Ok, we have come to the conclusion that my previous answer was right:
Because you use the name keys, it replaces the old keys with the new keys.
$db->assign('user_id', "1");
basically does this:
$this->fields['user_id] = (1)==""?("'1'"):1;
And when you got to do it again, it replaces it
$this->fields['user_id'] = (2)==""?("'2'"):2;
Try doing an assign, and then only assign the user_id again, there rest of the data will stay the same.
To fix this problem, we would call the $this->reset() function after a query.
public function query($_query){
$this->query = $_query;
$this->result = #mysql_query($_query, $this->link_id) or die( $_query."<p>".mysql_error($this->link_id) );
$this->reset();
return $this->result;
}
or you could call it in the individual insert or update functions:
public function insert($table){
// .... stuff
$this->query($sql);
$this->reset();
return $this->insert_id();
}
The other possibility is that the original programmer didn't convey his intent to you well enough. He might expect you to call $db->reset() after every query.