I have recently written a script that writes to a database every ten minutes. The aim of the script is to update the record if a record for that user exists else it inserts a record for that user.
The database has three important fields, id username ip.
As the IP changes the script will update the IP field for that user or add a record for the user with the new IP and on the next run update the IP rather than add a record.
I am trying to find out the best way to know if the record exists so that I can update rather than insert.
I was previously using $f->rowCount() after the update statement ie
if($f->rowCount() > 0){ continue; }else{ the insert statement}
So if the affected rows were greater than 0 I know the record exists. However, if the IP had not changed, effectively no update ever took place and the $f->rowCount() would be equal to 0.
Now I am simply select all records and store them in an array, check if the username is in_array if so do an update, else do an insert. Is there a better or quicker way to ensure whether a record exists, without having to do a select?
My question is really to see if something similar to $f->rowCount() exists as I only discovered this the other day. Many thanks
You could possibly make those three columns into a unique key. This would allow you to use the
INSERT ... ON DUPLICATE KEY query expression:
INSERT INTO `users`
([other fields], `username`, `ip`)
VALUES ([values for other fields], [user name], [IP address])
ON DUPLICATE KEY
UPDATE [other fields]=[other values], `username`=[user name], `ip`=[IP address]
silkfire's answer is looking very promising. but if you don't wanna change the unique keys in your table, why not change the query for your rowCount()?
for example:
SELECT COUNT(*) FROM `users`
WHERE `ip` = [IP address]
AND 'username' = [user name]
looks like you are using PDO; so with $f->fetchColumn() on this statement you'll know if this exact combination already exists. you will also know if it exists more than once.
Related
I'm trying my make an invitation system together with the Facebook PHP APK. What I basically want is when you have invited your friends, Facebook redirects back to your website with an array containing userIDs from friends that the user invited. I wish to loop through them and insert the invitations into a database. The looping and all is already under control but the database query isn't.
This is what my current query looks like:
SELECT `iID` FROM `invitations` WHERE `iSender` = 'SENDER' AND `iReceiver` = 'RECEIVER';
If that returns zero rows I process this query:
INSERT INTO `invitations` (`iSender`, `iReceiver`) VALUES ('SENDER', 'RECEIVER');
And then I check if they're already signed up to my website:
SELECT `uID` FROM `users` WHERE `uOAuthID` = 'RECEIVER';
If it returns more then 1 row I run the following and final query:
UPDATE `invitations` SET `iProcessed` = 1 WHERE `iReceiver` = 'RECEIVER';
So basically this is how the process is currently shaped:
If the user hasn't already has been invited by the inviter we insert the invitation into the database.
Then we check if the invited user already is signed up.
If he is signed up we update the invitation and say that it already has been processed.
I guess there's a better and faster method to do this with just maybe 1 or 2 queries. I've tried using INSERT IGNORE and ON DUPLICATE but that just gave me errors and gave up.
I hope that you understand what I'm looking for. Thank you all for your time!
There are to thing that suite your needs:
INSERT ... ON DUPLICATE KEY UPDATE Syntax:
If you specify ON DUPLICATE KEY UPDATE, and a row is inserted that
would cause a duplicate value in a UNIQUE index or PRIMARY KEY, an
UPDATE of the old row is performed. For example, if column a is
declared as UNIQUE and contains the value 1, the following two
statements have identical effect:
INSERT INTO table (a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE
c=c+1;
INSERT IGNORE:
If you use the IGNORE keyword, errors that occur while executing the
INSERT statement are ignored. For example, without IGNORE, a row that
duplicates an existing UNIQUE index or PRIMARY KEY value in the table
causes a duplicate-key error and the statement is aborted. With
IGNORE, the row still is not inserted, but no error occurs.
I'm using PHP/MySQL. What is the best way to check if the username is already taken?
Right now all I do is execute a select statement to see if the username is already taken. If the select returns a row then I stop and display an error message. If the select doesn't return anything then I insert the new user.
But is there a more efficient way? For example can I use UNIQUE on the username column and then only execute an insert statement(and get an error from the insert if it's already taken).
You do have the risk that some other thread will insert your user name in the brief moment between your SELECT confirming that the user doesn't exist and the INSERT where you insert it. This is called a race condition.
It may seem like the chance of that happening is slight, but there's a saying about that: "One in a million is next Tuesday."
Yes, a UNIQUE constraint on the username can prevent you from INSERTing a duplicate username. Declare that constraint.
Some articles will tell you that once you have a UNIQUE constraint, you don't have to do the SELECT anymore. Just try the INSERT and if it conflicts, then report the error.
However, I helped a site recover from a problem caused by this technique. On their site, users were attempting to create new usernames very quickly, and resulting in a lot of conflicts on the INSERT. The problem was that MySQL increments the auto-increment primary key for the table, even if the INSERT was canceled. And these auto-increment values are never reused. So the result was that they were losing 1000-1500 id numbers for every INSERT that succeeded. They called me when their INT primary key reached 231-1 and they couldn't create another row in the table!
The solution they used was first to try the SELECT, and report a duplicate. Then if it seemed safe, try the INSERT -- but write the code to handle the possible conflict with the UNIQUE constraint anyway, because the race condition can still occur.
I'm having a spot of trouble with a bit of code meant to find duplicates of a name along with the platform. This will also be adapted to find unique IDs later on.
So for example, if there is a server named "Apple" on the Xbox and you try to insert a record with the name "Apple" with the same platform it will reject it. However, another platform with the same name is allowed, such as "Apple" with PS3.
I've tried coming up with ideas and searching for answers, but I'm kind of in the dark as to what is the best way to go about checking for duplicates.
So far this is what I have:
$nameDuplicate_sql = $db->prepare("SELECT * FROM `servers` WHERE name=':name' AND platform=':platform'");
$nameDuplicate_sql->bindValue(':name', $name);
$nameDuplicate_sql->bindValue(':platform', $platform);
$nameDuplicate_sql->execute();
I've tried a bunch of different solutions, some from here, others from the PHP's manual and etc. None appear to work though.
I'm trying to stick with PDO, however, this is one instance where I cannot figure out where to turn. If this was in mysql_* I probably could just use mysql_affected_rows, but with PDO I have no clue. rowCount seemed promising, but it always returns 0 since this is neither an INSERT, UPDATE, or DELETE statement.
Oh, and I've tried the SQL statement in phpMyAdmin and it works; I tried it with a simple name/platform and it found rows properly.
If anyone can help me out here I'd appreciate it.
For most databases, PDOStatement::rowCount() does not return the
number of rows affected by a SELECT statement.
Instead, use PDO::query() to issue a SELECT COUNT(*) statement with the same predicates as your intended SELECT statement, then use
PDOStatement::fetchColumn() to retrieve the number of rows that will
be returned.
Your application can then perform the correct action.
Instead of checking for duplicates, why not just enforce it on the database table directly? Create a composite key that will prohibit entries being made if they are already there?
CREATE TABLE servers (
serverName varchar(50),
platform varchar(50),
PRIMARY KEY (serverName, platform)
)
This way, you will never get duplicates, and it also allows you to use the mysql insert... on duplicate key update... syntax which sounds like it might be rather handy for you.
If you already have a Primary Key on it or you don't want to make a new table, you can use the following:
ALTER TABLE servers DROP PRIMARY KEY, ADD PRIMARY KEY(serverName, platform);
Edit: A primary key is either a single row or a number of rows that have to have unique data in them. A single row cannot have the same value twice, but a composite key (which is what I am suggesting here) means that between the two columns, the same data cannot appear.
In this case, what you want to do, add in a server name and have it associated with a platform - the table will let you add in as many rows containing the same server name - as long as each one has a unique platform associated with it - and vice versa, you can have a platform listed as many times as you like, as long as all the server names are unique.
If you try to insert a record where the same servername/platform combination exists, the database simply won't let you do it. There is another golden benefit though. Due to this key constraint - mysql allows a special type of query to be used. It is the insert... on duplicate key update syntax. That means if you try to insert the same data twice (ie, database says no) you can catch it and update the row you already have in the table. For example:
You have a row with serverName=Fluffeh and it is on platform=Boosh but you don't know about it right now, so you try to insert a record with the intention of updating the server IP address.
Normally you would simply write something like this:
insert into servers (serverName, platform, IPAddress)
values ('$serverName', '$platform', '$IPAddy')
But with a nice primary key identified you can do this:
insert into servers (serverName, platform, IPAddress)
values ('$serverName', '$platform', '$IPAddy')
on duplicate key update set IPAddress='$IPAddy';
The second query will insert the row with all the data if it doesn't exist already. If it doesm, Bam! it will update the IP Address of the server which was your intention all along.
Remove the single quotes from your query on the parameter tokens... they will be quoted once they are bound... thats part of the reason for a prepared statement.
$nameDuplicate_sql = $db->prepare("SELECT * FROM `servers` WHERE name= :name AND platform= :platform");
I have a form and a database table named reports. I have a date field (primary key) and a textarea named changes to say what's been changed on that date. If the date is the same I want to be able to overwrite the information in the 'changes' column for that date.
My insert command, which works on its own, is as follows:
mysql_query("
INSERT INTO reports (thedate,changes)
VALUES ('$_POST[thedate]','$_POST[changes]')
");
I understand that I'll need to use ON DUPLICATE KEY UPDATE after my INSERT but after numerous attempts I cannot get it right. Not only do things no update but it seems to break my insert command so even a new row isn't added to the database.
Apologies if this is a duplicate question. After lots of searching and lots of trying I cannot get it to work.
Have a look att REPLACE.
REPLACE works exactly like INSERT, except that if an old row in the
table has the same value as a new row for a PRIMARY KEY or a UNIQUE
index, the old row is deleted before the new row is inserted.
Note that if you have a foreign key with an action ON DELETE it will be triggered when using REPLACE since it does a delete followed by an insert .
Using ON DUPLICATE KEY it could look like this:
INSERT reports (thedate, changes) VALUES ('$_POST[thedate]', '$_POST[changes]')
ON DUPLICATE KEY UPDATE changes = '$_POST[changes]'
This is the plain SQL query:
INSERT INTO reports (thedate, changes)
VALUES ('2011-11-10', 'Lorem ipsum')
ON DUPLICATE KEY UPDATE changes=VALUES(changes)
http://dev.mysql.com/doc/refman/5.5/en/insert-on-duplicate.html
Now, you absolutely need to sit down and try to understand what SQL is and how it interacts with PHP and differs from it. You are using PHP to compose strings that happen to be code from another language called SQL. The way you are doing it, the resulting code can be valid SQL or not, and it'll depend of the arbitrary data sent by any anonymous visitor. In the best case, your script will crash. In the worse case, the visitor will be able to read confidential data or alter your database. Here's the example in the manual page for mysql_query():
// This could be supplied by a user, for example
$firstname = 'fred';
$lastname = 'fox';
// Formulate Query
// This is the best way to perform an SQL query
// For more examples, see mysql_real_escape_string()
$query = sprintf("SELECT firstname, lastname, address, age FROM friends
WHERE firstname='%s' AND lastname='%s'",
mysql_real_escape_string($firstname),
mysql_real_escape_string($lastname));
// Perform Query
$result = mysql_query($query);
This should work:
mysql_query("INSERT INTO reports (thedate,changes) VALUES ('$_POST[thedate]','$_POST[changes]') ON DUPLICATE KEY UPDATE changes=VALUES(changes)");
Disadvantage of using REPLACE is that it's not standard SQL but a MySQL extension. Beside that, when using auto incremented columns, REPLACE will reinsert with a different value. I won't recommend it to anyone.
Edit: ON DUPLICATE KEY isn't standard SQL either. Sorry!
I have a table named user_ips to keep track of users in case they delete their cookies or change browser. So anyway, the following code is simple. It updates entries in user_ips that are equal to the user's id and IP. If the query did not update any rows, then it means that IP for that user is not in the table, so it inserts it.
$site->query('UPDATE `user_ips` SET `last_time` = UNIX_TIMESTAMP(), `user_agent` = \''.$this->user_agent.'\' WHERE `ip` = '.$this->ip.' AND `userid` = '.$this->id);
if(mysql_affected_rows() == 0)
{
$site->query('INSERT INTO `user_ips` SET `userid` = '.$this->id.', `ip` = '.$this->ip.', `first_time` = UNIX_TIMESTAMP(), `last_time` = UNIX_TIMESTAMP(), `user_agent` = \''.$this->user_agent.'\'');
}
The problem is mysql_affected_rows() sometimes returns 0 even if a row with the user's current ID and IP exists. So then the code adds another row to the table with the same IP.
In case you are wondering, $site is mysql class I made for my website and the only query it executes is the one passed to it by query(), and nothing more, so this is not a problem with the class. Oh and the IP is stored as a long IP, so it does not need quotes around it.
I'm directly quoting the PHP documentation here:
When using UPDATE, MySQL will not update columns where the new value is the same as the old value. This creates the possibility that mysql_affected_rows() may not actually equal the number of rows matched, only the number of rows that were literally affected by the query.
So in your case, mysql_affected_rows() will return 0 when UNIX_TIMESTAMP() returns the same value (for example, two requests from the same client in the same second).
To build on slipbull's answer, the simplest way to handle this is perhaps to simply perform a SELECT query to evaluate whether or not an INSERT is necessary. Another solution would be to simply INSERT a record at user creation, as this would guarantee a valid record.
You could just set your primary key to span both userid and ip. This would ensure you get no duplicate entries, but you would have to suppress the error on the insert query if you are not going to add a select to check a record exists.
Another option might be to use the MySQL REPLACE command. http://dev.mysql.com/doc/refman/5.0/en/replace.html. Which will delete the row if it already exists, and then insert the row.
Although that might not be suitable for your precise needs.