multiple student registration at same time without data conflict in Codeigniter - php

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.

Related

Spending time optimizing an sql query VS Pushing unnecessary data from database

I have a considerably big table in my database, and I want to have a function that, for example, performs an UPDATE query.
What I used to do in my older projects was passing all the values for all the columns, and insert them in the query string, like this example:
function updateUser($id, $name, $username){
$query = "UPDATE user SET name = '{$name}', username = '{$username}' WHERE id = '{$id}' ";
return mysqli_query($this->conn, $query);
}
That meaning, every column was being altered even those that weren't changed.
But, this time being a big table, I don't want to sacrifice the application speed.
What I'm trying to do is make some comparisons, therefore optimizing the query, and only then sending it to the database.
Staying in the UPDATE query example from before, this is kind of what I want to do:
function updateUser($old_user, $new_user, $user_id){
$changed = false;
$oldFirstName = $old_user->getFirstName();
$newFirstName = $new_user->getFirstName();
if($oldFirstName == $newFirstName){
$firstNameQuery = "";
}else{
$firstNameQuery = " first_name = '".mysqli_escape_string($this->conn, $newFirstName)."',";
$changed = true;
}
$oldLastName = $old_user->getLastName();
$newLastName = $new_user->getLastName();
if($oldLastName == $newLastName){
$lastNameQuery = "";
}else{
$lastNameQuery = " last_name = '".mysqli_escape_string($this->conn, $newLastName)."',";
$changed = true;
}
$oldEmail = $old_user->getEmail();
$newEmail = $new_user->getEmail();
if($oldEmail == $newEmail){
$emailQuery = "";
}else{
$emailQuery = " email = '".mysqli_escape_string($this->conn, $newEmail)."',";
$changed = true;
}
if($changed){
$query = "UPDATE user SET {$firstNameQuery}{$lastNameQuery}{$emailQuery} WHERE user_id = {$user_id}";
return mysqli_query($this->conn, $query);
}else{
return 0;
}
}
Although, as you can see, as the table grows this function gets bigger and with a lot more comparisons and attributions.
My question is: Am I saving a noticeable amount of time doing this, or it isn't whorth it?
You are probably making the code less efficient. Much of the time for an update is on logging the transaction and physically storing the data page for each record. Because all the columns for a single record are (typically) stored on a single page, updating one column or many columns doesn't matter.
On the other hand, the additional comparisons in the application also take time.
Of course -- as with any performance related issue -- you can test the different scenarios. I wouldn't expect any noticeable improvement in performance by going through such logic to reduce the number of columns in the update, unless it eliminated entirely the need for updating certain rows.

How to check for time/date conflicts (PHP/MySQL)

We currently have a system, where we have a function that checks for date/time conflicts, that we manually populate all possible date/time conflicts as parameters to.
ie:
//session conflicts checker
function chkConflicts($sessions) {
if (!is_array($sessions)) {
$arrsessions = explode(',', $sessions);
} else {
$arrsessions = $sessions;
}
$conflictcount = 0;
foreach ($arrsessions as $thissession) {
if (($_POST[trim($thissession)] != '' && $_POST[trim($thissession)] != 0) || $_POST[trim($thissession) . '_faculty'] != '' && $_POST[trim($thissession) . '_faculty'] != 0) {
$conflictcount++;
}
}
if ($conflictcount > 1) {
return false;
} else {
return true;
}
}
used like so:
if (!chkConflicts('hours_5_p02_800, hours_5_p03_800, hours_5_p07_800, hours_5_p04_800')) {
$errmsg .= 'There is a conflict at 8:00am in the selections. ';
}
its very tedious, and time consuming to find these as well as manually populate the functions/params..
I need a new approach! I'm hoping to just get a list of the conflicts back and highlight the (table) row on the page with a message about you have conflicts in the highlighted areas (less specific then giving an exact time..etc, and still gets the job done for the user)
All the user interaction is done by checkboxes, that have a name that reflects the column in the table:
ie:
hours_5_p02_800, hours_5_p03_800, hours_5_p07_800,
hours_5_p04_800,hours_9_reg_session_300_845
(same names used in the chkConflicts function above)
I have (among others) two columns: sessiondate varchar(255) & presentationtime varchar(255) respectfully.
With the session date data looking like: 9/9/2015
And the presentation time data looking like: 8:45 AM - 9:05 AM (not sure if this matters, but including it for the sake of full disclosure)
I dont have ALOT of control over the database, but I could probably get the times split into two columns (start/end) if that would be best?
before ANY chkConflict function is called.. the 'selections' of the user are recorded/saved to the table.. AND THEN the conflict check is called.
//record hours for each day
function recordHours() {
$arrflds = explode(',', $_POST['fieldlist']);
$sql = "UPDATE {$this->eventcode}_cme SET";
foreach($arrflds as $key) {
$sql .= " " . addslashes(trim($key)) . " = '" . addslashes($_POST[trim($key)]) . "',";
}
$sql .= " lastupdated = '" . date('Y-m-d H:i:s') . "' WHERE id = '" . $_SESSION[$this->eventcode . '_id'] . "'";
$this->debugout .= ($this->debug) ? 'Record hours: ' . $sql . '<br>' . $this->crlf : '';
$result = mysql_query($sql) or exit('Error recording hours: ' . mysql_error());
}
*I'm updating things to PDO after I get the conflict stuff figured out. (thanks)
I dont mind this, because the chkConflict function (even though the choices have been saved) does NOT let the user move ahead until the error(s) message is taken care of (hence updating the table again when the conflicts are resolved)..
I'm thinking I'll need to no longer use the the chkConflict method and alter the recordHours function to not only update the table.. but because it has the 'fieldlist' array that was posted.. that I'll need to do the conflict checking there as well... or possibly call another function from withing recordHours and pass along the same fieldlist...
The column data is not really used for saving or (current) conflict checking of any sort... the column NAME is.
My problem is I'm not sure how to go do the date/time conflict check?
re-cap: fieldlist and column names are named like:
ie: hours_5_p02_800, hours_5_p03_800, hours_5_p07_800,hours_5_p04_800,hours_9_reg_session_300_845
(and is a 24 hour format for the time)
ex: hours_9_reg_session_300_845
9 = date
reg = event code
300 = session code
845 = session time(24-hour format)
Upon thinking more, its more like I need to do some sort of string parsing in PHP (on the fieldlist names) and do conversion/checking on that?
I need to take the string, break it down into its parts and do some sort of (concatenate/string building) comparison on it?
basically I get list of the fields being submitted that are formatted as above and match the table column names...
how can I pass this same fieldlist over to a new function (or whatever) to get any conflicts back?
Since you seem have the chance to change things a little I would suggest you to create the 2 fields but as timestamps field type. One for the start and one for the end.
You can look at timestamps fields as a date/time like 2015-10-17 08:45:00 or, using UNIX_TIMESTAMP('2015-10-17 08:45:00'), as an integer like 1445064300 which is exactly the same date/time info.
In both cases you can do things like
SELECT :yourdatetime BETWEEN date_time_start AND date_time_end;
or
SELECT :yourunixlikedatetime
NOT BETWEEN UNIX_TIMESTAMP(date_time_start) AND UNIX_TIMESTAMP(date_time_end);
for instance...

Weird auto increment issue

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

If else in sql insert

I want to make a code where if the data already exists in the database and the user insert the same input again and send to the database, the sql command will detect it and will not allow the duplicate data enter the database. Addtional information, I don`t have primary key for my table. Here is my code.
$sql="INSERT IGNORE INTO tempahan(Nama,Aktiviti,No_HP,Unit,Tempat,Tarikh_Penggunaan,Masa_Mula,Masa_Akhir,Email) VALUES('$_POST[name]','$_POST[Aktiviti]','$_POST[number]','$_POST[unit]','$_POST[tempat]','$_POST[tarikh]','$_POST[masa1]','$_POST[masa2]','$_POST[email]')";
$_POST['tempat'] = $data['Tempat'] ;
$_POST['masa1'] = $data['Masa_Mula'];
$_POST['masa2'] = $data['Masa_Akhir']; if($_POST['tempat'] != $data['Tempat'] && $_POST['masa1'] != $data['Masa_Mula'] && $_POST['masa2'] != $data['Masa_Akhir']) {
echo 'the booking was successful.';
}
else
{ echo 'the place already occupied.';}
I'm new to sql and also php. Therefore, I really need help from all of you guys. I already see the other same question. But, every solution provided I've failed.
The correct way to do this is to enforce a unique constraint on your table, across the fields that you consider to be unique. You can do that as such.
alter table tempahan
add unique (Tempat, Masa_Mula, Masa_Akhir)
Your database will then reject out of hand any attempts to insert duplicate data. No need to do a prior check before inserting.
Here is a very basic demo of what happens when you set your table up with this unique constraint, and then try and insert duplicate data. In short: it errors.
$query = $db->query( // query your table );
$array = array('name'=>$_POST['name'],
'address'=>$_POST['address']);
while ($row = mysqli_fetch_all($query)) {
$diff = in_array($array, $row);
{
if(empty($diff))
{
// insert data into table
}
else{
//data already exist
}
}
}
// first check existing recors on the database
$select = "SELECT `Tempat`, `Masa_Mula`, `Masa_Akhir`
FROM `tempahan`
WHERE `Tempat` = {$_POST['tempat']}
AND `Masa_Mula` = {$_POST['masa1']}
AND `Masa_Akhir` = {$_POST['masa2']}";
$result = mysql_query($select, $dbconnection);
// check if the have existing records
// the query fetching depends on your work
// but this is a simple way only
// but have more examples on the internet
// to make query more better and ellegant
if (mysql_num_rows($select) > 0) {
echo 'the place already occupied.';
} else {
// insert new record
$sql="INSERT IGNORE INTO tempahan(Nama,Aktiviti,No_HP,Unit,Tempat,Tarikh_Penggunaan,Masa_Mula,Masa_Akhir,Email)
VALUES(
'$_POST[name]',
'$_POST[Aktiviti]',
'$_POST[number]',
'$_POST[unit]',
'$_POST[tempat]',
'$_POST[tarikh]',
'$_POST[masa1]',
'$_POST[masa2]',
'$_POST[email]')";
echo 'the booking was successful.';
}

MySQL double insert happeing in my php application

I have php application which gets information from a SAML POST and creates a record in the MySQL database, if the record is already present it just updates it
Here is the code
//getMemberRecord returns true for successful insertion.
$row = $this->getMemberRecord($data);
if ($row) {
//if the row already exists
$this->updateMemberRecord($data)
} else {
// creates a new record
$this->setMemberRecord($data);
}
This code is causing double inserts in the database, we don't have a unique key for the table due to some poor design constraints, but I see two HTTP posts in the access logs happening at the same time.
The create date column is same or differs by a second for the duplicate record.
This issue is happening for only select few, it works for most of them.
The table is innoDB table and we can not use sessions on our architecture.
Any ideas of why this would happen
You said:
I see two HTTP posts in the access logs
You should try avoiding this and have just one http POST invocation
May be it is a problem related to concurrency and mutual exclusion. The provided code must be executed in a mutually exclusion zone, so you must use some semaphore / mutex to prevent simultaneous execution.
If you have two HTTP POST happening your problem is not on the PHP/MYSQL side.
One thing is allowing a second 'transparent' HTTP POST in the HTTP protocol. It's the empty url. If you have an empty GET url in the page most browsers will replay the request which rendered the page. Some recent browser are not doing it, but most of them are still doing it (and it's the official way of HTTP). An empty GET url on a page is for example <img src=""> or < script url=""> but also an url() in a css file.
The fact you have one second between the two posts make me think it's what's happening for you. The POST response page is quite certainly containing an empty Get that the browser fill by replaying the POST... I hate this behaviour.
I found that the double inserts were happening becuase double submits and our application doesnot handle double submits efficiently, I read up on some articles on this, here are some of the solutions
it always best to handle double posts at the server side
best solution is to set a UNIQUE KEY on the table or do a INSERT ON DUPLICATE KEY UPDATE
if you have sessions then use the unique token , one of the technique in this article
http://www.freeopenbook.com/php-hacks/phphks-CHP-6-SECT-6.html
or use can use the Post/Redirect/Get technique which will handle most double submit problems
http://en.wikipedia.org/wiki/Post/Redirect/Get
note: the Double submit problem only happens on a POST request, GET request is immune
public function setMemberRecord($data, $brand_id, $organization_id, $context = null)
{
global $gRegDbManager;
$sql = "insert into member ......"
$gRegDbManager->DbQuery($sql);
// Popuplate the iid from the insert
$params['iid'] = $gRegDbManager->DbLastInsertId();
$data = some operations
return (int)$data;
}
public function getMemberRecord($field, $id, $brand_id, $organization_id, $organization_level_account = null)
{
global $gRegDbManager;
$field = mysql_escape_string($field);
$id = mysql_escape_string($id);
$sql = "SELECT * FROM " . DB_REGISTRATION_DATABASE . ".member WHERE $field = '$id' ";
if($organization_level_account) {
$sql .= "AND organization_fk = " . $organization_id;
} else {
$sql .= "AND brand_fk = " . $brand_id;
}
$sql .= " LIMIT 1";
$results = $gRegDbManager->DbGetAll($sql);
if(count($results) > 0) {
return $results[0];
}
return;
}
/* * ******************************************************************************************************
* Updates member record in the member table
* *******************************************************************************************************
*/
public function updateMemberRecord($id, $changes)
{
global $gRegDbManager;
$id = mysql_escape_string($id);
if(!empty($changes)) {
$sql = "UPDATE " . DB_REGISTRATION_DATABASE . ".member SET ";
foreach($changes as $field => $value) {
$sql .= mysql_escape_string($field) . " = '" . mysql_escape_string($value) . "', ";
}
$sql = rtrim($sql, ", ");
$sql .= " WHERE iid = '$id'";
$gRegDbManager->DbQuery($sql);
} else {
return false;
}
}

Categories