Currently I have an application whereby when the user click the submit button, it will trigger the insert code to insert the record for reports function.
The problem I'm facing now is, the user not a computer savvy, therefore I've an inaccurate data from the table. Below are the few examples that cause the data capture incorrectly.
Case 1: Once the user click the submit button, the data already insert to the database but the user accidentally hits the submit button again and the insert trigger again and causing the database has two same records with a different timestamp in about 5 to 10sec.
Case 2: The first record user enter is actually not the user wants, for example, the user wants to key-in 29/10/2013 but the user key-in as 28/10/2013 and the user again hit the back button and insert the correct one which is 29/10/2013 and a single transaction it have 2 record which is one is the correct date and one is the mistake date.
Below are the code:
$stmt = $db->prepare("INSERT INTO log (dob, log_datetime, log_count, amount) VALUES (?, ?, ?, ?)");
$stmt->bindParam(1, $userDate);
$stmt->bindParam(2, $now, PDO::PARAM_STR);
$stmt->bindParam(3, $cust_count);
$stmt->bindParam(4, $amount);
date_default_timezone_set('Asia/Kuala_Lumpur');
$now = date('Y-m-d H:i:s');
$cust_count = 1;
$amount = 10;
$stmt->execute();
The code above trigger whenever the user hits the submit button, so I was thinking is there possible to make the insert code to trigger after a period of time, example 30sec?
Additional Notes
This is an application developed and execute the database from the table
It has nothing to do with form validation, as I already coded the form validation part.
My question is is there any way to trigger the insert query on specific amount of time set
I search in SO and Google, but I couldn't find a relevant example to work with.
Please suggest me any method that can fix the above two case scenarios.
Thank you.
For the first case, disable the button when the form is submited.
For the second case, validate the date before inserting it into the database.
http://us3.php.net/sleep
sleep(30);
$stmt->execute();
Will do what you want I believe.
MySQL doesn't have that behavior. I think that what you are looking for is an asynchronous task queue, like Celery
Disable the submit button upon submitting, and on the post-submission page, show the user the submitted data and give them a clear way to edit it.
Users generally suspect that hitting back into a form is not a good idea, but you're not giving them any better options.
Trying to catch all UI leaks can prove difficult and cumbersome.
I would rather use the timestamp in the DB as a temporal filter
I would query the most recent records (in the last 30 seconds) using cust_count as a candidate key, and update the last record (if any) instead of creating it anew.
Schematic example:
function update_log ($entry)
{
$recent = $db->select (
"id FROM log
WHERE timestamp > now()-30 seconds
AND user = $entry->user");
if ($recent)
{
// overwrite last record with current entry (and refresh timestamp)
}
else
{
// create a new entry
}
}
You could even delete all logs of the user adding an entry that are newer than 30 seconds.
Related
I have a game website and I want to update the users money, however if I use 2 pc's at the exact same time this code will execute twice and the user will be left with minus money. How can I stop this from happening? It's driving me crazy.
$db = getDB();
$sql = "UPDATE users SET money = money- :money WHERE username=:user";
$stmt = $db->prepare($sql);
$stmt->bindParam(':money', $amount, PDO::PARAM_STR);
$stmt->bindParam(':user', $user, PDO::PARAM_STR);
$stmt->execute();
Any help is appreciated.
Echoing the comment from #GarryWelding: the database update isn't an appropriate place in the code to handle the use case that is described. Locking a row in the user table isn't the right fix.
Back up a step. It sounds like we are wanting some fine grained control over user purchases. Seems like we need a place to store a record of user purchases, and then we can can check that.
Without diving into a database design, I'm going to throw out some ideas here...
In addition to the "user" entity
user
username
account_balance
Seems like we are interested in some information about purchases a user has made. I'm throwing out some ideas about the information/attributes that might be of interest to us, not making any claim that these are all needed for your use case:
user_purchase
username that made the purchase
items/services purchased
datetime the purchase was originated
money_amount of the purchase
computer/session the purchase was made from
status (completed, rejected, ...)
reason (e.g. purchase is rejected, "insufficient funds", "duplicate item"
We don't want to try to track all of that information in the "account balance" of a user, especially since there can be multiple purchases from a user.
If our use case is much simpler than that, and we only to keep track of the most recent purchase by a user, then we could record that in the user entity.
user
username
account_balance ("money")
most_recent_purchase
_datetime
_item_service
_amount ("money")
_from_computer/session
And then with each purchase, we could record the new account_balance, and overwrite the previous "most recent purchase" information
If all we care about is preventing multiple purchases "at the same time", we need to define that... does that mean within the same exact microsecond? within 10 milliseconds?
Do we only want to prevent "duplicate" purchases from different computers/sessions? What about two duplicate requests on the same session?
This is not how I would solve the problem. But to answer the question you asked, if we go with a simple use case - "prevent two purchases within a millisecond of each other", and we want to do this in an UPDATE of user table
Given a table definition like this:
user
username datatype NOT NULL PRIMARY KEY
account_balance datatype NOT NULL
most_recent_purchase_dt DATETIME(6) NOT NULL COMMENT 'most recent purchase dt)
with the datetime (down to the microsecond) of the most recent purchase recorded in the user table (using the time returned by the database)
UPDATE user u
SET u.most_recent_purchase_dt = NOW(6)
, u.account_balance = u.account_balance - :money1
WHERE u.username = :user
AND u.account_balance >= :money2
AND NOT ( u.most_recent_purchase_dt >= NOW(6) + INTERVAL -1000 MICROSECOND
AND u.most_recent_purchase_dt < NOW(6) + INTERVAL +1001 MICROSECOND
)
We can then detect the number of rows affected by the statement.
If we get zero rows affected, then either :user wasn't found, or :money2 was greater than the account balance, or most_recent_purchase_dt was within a range of +/- 1 millisecond of now. We can't tell which.
If more than zero rows are affected, then we know that an update occurred.
EDIT
To emphasize some key points which might have been overlooked...
The example SQL is expecting support for fractional seconds, which requires MySQL 5.7 or later. In 5.6 and earlier, DATETIME resolution was only down to the second. (Note column definition in the example table and SQL specifies resolution down to microsecond... DATETIME(6) and NOW(6).
The example SQL statement is expecting username to be the PRIMARY KEY or a UNIQUE key in the user table. This is noted (but not highlighted) in the example table definition.
The example SQL statement overrides update of user for two statements executed within one millisecond of each other. For testing, change that millisecond resolution to a longer interval. for example, change it to one minute.
That is, change the two occurrences of 1000 MICROSECOND to 60 SECOND.
A few other notes: use bindValue in place of bindParam (since we're providing values to the statement, not returning values from the statement.
Also make sure PDO is set to throw an exception when an error occurs (if we aren't going to check the return from the PDO functions in the code) so the code isn't putting it's (figurative) pinky finger to the corner of our mouth Dr.Evil style "I just assume it will all go to plan. What?")
# enable PDO exceptions
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = "
UPDATE user u
SET u.most_recent_purchase_dt = NOW(6)
, u.account_balance = u.account_balance - :money1
WHERE u.username = :user
AND u.account_balance >= :money2
AND NOT ( u.most_recent_purchase_dt >= NOW(6) + INTERVAL -60 SECOND
AND u.most_recent_purchase_dt < NOW(6) + INTERVAL +60 SECOND
)";
$sth = $dbh->prepare($sql)
$sth->bindValue(':money1', $amount, PDO::PARAM_STR);
$sth->bindValue(':money2', $amount, PDO::PARAM_STR);
$sth->bindValue(':user', $user, PDO::PARAM_STR);
$sth->execute();
# check if row was updated, and take appropriate action
$nrows = $sth->rowCount();
if( $nrows > 0 ) {
// row was updated, purchase successful
} else {
// row was not updated, purchase unsuccessful
}
And to emphasize a point I made earlier, "lock the row" is not the right approach to solving the problem. And doing the check the way I demonstrated in the example, doesn't tell us the reason the purchase was unsuccessful (insufficient funds or within specified timeframe of preceding purchase.)
for the negative balance change your code to
$sql = "UPDATE users SET money = money- :money WHERE username=:user AND money >= :money";
First idea:
If you're using InnoDB, you can use transactions to provide fine-grained mutual exclusion. Example:
START TRANSACTION;
UPDATE users SET money = money- :money WHERE username=:user;
COMMIT;
If you're using MyISAM, you can use LOCK TABLE to prevent B from accessing the table until A finishes making its changes. Example:
LOCK TABLE t WRITE;
UPDATE users SET money = money- :money WHERE username=:user;
Second idea:
If update don't work, you may delete and insert new row (if you have auto increment id, there won't be duplicates).
I have two pages. One is a form that I use to simply input data that will be sent to my database and the second page that actually takes the data inputted into the form and sends it to the database and is supposed to display the information that I've just added.
Everything works fine, however I'm struggling with the query slightly. What I need it to do is display all the information for the last data inputted to the database.
The query I currently have just displays the data with the highest ID:
$sql = "SELECT * FROM Results ORDER BY ID DESC LIMIT 1";
So as an example I would be left with the following information after completing my form:
Success! Data being saved:
ID = 900 Amount = 206 Date = 2016-12-26
This is obviously just showing the data with the highest ID, but since the ID and all the data fluctuates, I need it to just show the data that has just been inputted.
I came accross this: Query to select newly added records only. But I don't believe this soultion to be viable as the database is external and I don't want to be creating new tables.
I was thinking that it might be possible to assign a hidden value to each newly added record via the query. e.g. New 1, New 2, New 3 etc. Then printing the latest record for New. However, I couldn't find anything on how to do this.
Any help would be greatly appreciated!
You must use this method to have very correct value:
Input form must send to another file that do inserting (we call it here insert.php)
insert.php must insert the data after validation and after that you can fetch the last ID number from database. Depending on the method you are working with it can be different. for example if you are using PDO you can get it by PDO::lastInsertId
after getting the ID you need to forward it to the viewing or editing page. for example view.php?id=LastInsertId. This forward have some reasons:
Codes can be cleaner.
We prevent refresh and resend inserting. for example if you do inserting inside view.php and user hit F5 to refresh the page, The insertion happening again.
This is the whole idea. you can use this method for only one page:
page.php?do=new
page.php?do=insert
forward to the page.php?do=view&id=lastInsertID
why you trying to get just inputted data from database? you can do it using HTTP POST/GET method easily.just send data as parameters and show them in second page.
If you already have the data you are inserting, you don't need to run a query to get it back from the database again, you could just ensure that the query was successful and display the data directly. Anyways:
You can get the insert ID from the last insert using the MySQLi object. For example:
$sql = "<your insert statement>"
$conn->query($sql);
$last_id = $conn->insert_id; //Id of the row you just inserted
$sql = "SELECT * FROM Results WHERE id=$last_id";
This is assuming you do the insert in the same page that you display the result.
This code works but the problem is that if several people use it simultaneously it will cause problems in the sense that some people wont be registered. So I need to rewrite this in a way that all queries per person are executed and finished before the queries of the next person start.
First, the code reads from the database in order to get to the string of all the people that are registered so far.
$sql_s = $con -> query("select * from schedule where date='$date'");
$row_schedule = $sql_s->fetch_array(MYSQLI_BOTH);
$participants = $row_schedule['participants'];
$participants is a string that looks something like "'Sara':'Richard':'Greg'"
Now the current user (Fredrik) wants to add its name to the string like this
$current_user='Fredrik';
$participants_new=add_participant($participants,$current_user);
add_participant is a php function that adds 'Fredrik' to the participant string. Then I want to replace the old participant string with the new one in the SQL database like this
$sql = $con->query("UPDATE schedule SET participants='{$participants_new}' where date='{$date}'");
The specific problem is that if another person (Linda) reads the database before Fredrik executes
$sql = $con->query("UPDATE schedule SET participants='{$participants_new}' where date='{$date}'");
Linda won't get a string that includes Fredrik, she will get "'Sara':'Richard':'Greg'". And when she has added her name it will look like "'Sara':'Richard':'Greg':'Linda'" and when she updates the database like this
$sql = $con->query("UPDATE schedule SET participants='{$participants_new}' where date='{$date}'");
The string including Fredrik ("'Sara':'Richard':'Greg':'Fredrik'") will be overwritten with ("'Sara':'Richard':'Greg':'Linda'") and noone will ever know that Fredrik registered in the class.
Thus, how can I rewrite this code such that all Fredrik's queries are executed before Linda's queries start?
Your question is very good example, showing why one should always learn database design basics and always follow them.
A separator-delimited string in a database is a deadly sin. For many reasons, but we are interesting in this particular case.
Had you designed your database properly, adding participants into separate rows, there would be not a single problem.
So, just change your design by adding a table with participants, and there will be not a single problem adding or removing any number of them.
Here is an approach to do it :
Theoritically Explanation :
Something like this could work.That everytime when user executes the query so it should check for time the request was made to update the query so.Now there must be time difference between user requests for updation query.
Note : Still It's not guaranteed that it will work as because when you will be having internet problems and the user who submitted the request at first but having internet problems and that's why his update query execution is delayed during that time and the other user comes and he sent request late for the updation query but he was having no internet connection problem so his query will be updated before and I think hence that way first user query will get failed..!
Here is the Code :
<?php
// You need to add another column for saving time of last query execution
$current_time=time();
$current_date=date("Y-m-d",$t);
$query_execution_new_time = $current_time.":".$current_date;
if (empty($row_schedule['query_execution_time'])) {
$sql = $con->query("UPDATE schedule SET query_execution_time='{$query_execution_new_time}' where date='{$date}'");
} else {
$query_execution_time = explode(":",$row_schedule['query_execution_time']);
if ($query_execution_time[0] < $current_time) {
$con->query("UPDATE schedule SET participants='{$participants_new}' where date='{$date}'");
$sql = $con->query("UPDATE schedule SET query_execution_time='{$query_execution_new_time}' where date='{$date}'");
}
}
?>
Try this
No need to fetch first all participants and then update.
only update new participant user.
you can concat result of previous one result saved in database column field.
update schedule
set participants = case when participants is null or participants =''
then CONCAT(participants,'Fredrik') // assume Fredrik a new participant
else CONCAT(participants,':','Fredrik')
end
where date='$date';
That way even if you have multiple participants came at the same time the queries won't run at exactly the same time and so you'll get the correct user at the end.
you don't need to worry about multiple users clicking on them unless you've got millions of users
I have a small PHP function on my website which basically does 3 things:
check if user is logged in
if yes, check if he has the right to do this action (DB Select)
if yes, do the related action (DB Insert/Update)
If I have several users connected at the same time on my website that try to access this specific function, is there any possibility of concurrency problem, like we can have in Java for example? I've seen some examples about semaphore or native PHP synchronization, but is it relevant for this case?
My PHP code is below:
if ( user is logged ) {
sql execution : "SELECT....."
if(sql select give no results){
sql execution : "INSERT....."
}else if(sql select give 1 result){
if(selected column from result is >= 1){
sql execution : "UPDATE....."
}
}else{
nothing here....
}
}else{
nothing important here...
}
Each user who accesses your website is running a dedicated PHP process. So, you do not need semaphores or anything like that. Taking care of the simultaneous access issues is your database's problem.
Not in PHP. But you might have users inserting or updating the same content.
You have to make shure this does not happen.
So if you have them update their user profile only the user can access. No collision will occur.
BUT if they are editing content like in a Content-Management System... they can overwrite each others edits. Then you have to implement some locking mechanism.
For example(there are a lot of ways...) if you write an update on the content keeping the current time and user.
Then the user has a lock on the content for maybe 10 min. You should show the (in this case) 10 min countdown in the frontend to the user. And a cancel button to unlock the content and ... you probably get the idea
If another person tries to load the content in those 10 min .. it gets an error. "user xy is already... lock expires at xx:xx"
Hope this helps.
In general, it is not safe to decide whether to INSERT or UPDATE based on a SELECT result, because a concurrent PHP process can INSERT the row after you executed your SELECT and saw no row in the table.
There are two solutions. Solution number one is to use REPLACE or INSERT ... ON DUPLICATE KEY UPDATE. These two query types are "atomic" from perspective of your script, and solve most cases. REPLACE tries to insert the row, but if it hits a duplicate key it replaces the conflicting existing row with the values you provide, INSERT ... ON DUPLICATE KEY UPDATE is a little bit more sophisticated, but is used in a similar situations. See the documentation here:
http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html
http://dev.mysql.com/doc/refman/5.0/en/replace.html
For example, if you have a table product_descriptions, and want to insert a product with ID = 5 and a certain description, but if a product with ID 5 already exists, you want to update the description, then you can just execute the following query (assuming there's a UNIQUE or PRIMARY key on ID):
REPLACE INTO product_description (ID, description) VALUES(5, 'some description')
It will insert a new row with ID 5 if it does not exist yet, or will update the existing row with ID 5 if it already exists, which is probably exactly what you want.
If it is not, then approach number two is to use locking, like so:
query('LOCK TABLE users WRITE')
if (num_rows('SELECT * FROM users WHERE ...')) {
query('UPDATE users ...');
}
else {
query('INSERT INTO users ...');
}
query('UNLOCK TABLES')
I am creating a counter for unique number of visits on a post, so what I have until now is a table for storing data like this;
cvp_post_id | cvp_ip | cvp_user_id
In cases a registered user visits a post, for the first time a record is inserted with cpv_post_id and cvp_user_id, so for his next visit I query the table and if the record is available I do not count him as a new visitor.
In cases of an anonymous user the same happens but now the cvp_ip and cpv_post_id are used.
My concerns is that I do a query every time anyone visits a post for checking if there has been a visit, what would be a more effective way for doing this?
Create a unique index containing your three columns and execute your insert using IGNORE keyword:
INSERT IGNORE INTO your_table (cvp_post_id, cvp_user_id)
VALUES(1, 1);
Most users will only visit once so you avoid a SELECT followed by an INSERT.
There is a very simple way that can help you to save a lot of queries using sessions, it's should be something like that:
if(!isset($_SESSION['visited_post'][$yourPostID]))
{
//Perform your code
$_SESSION['visited_post'][$yourPostID] = true;
}
Make a session for the user and while the session is on you need to check but once (when opening the session). From now on when the user visits a post you already know he is in the database.