i'm trying to generate a unique id using this code:
$RCode = md5(uniqid(rand(), true));
then i want it to check in my database if the RCode is unique.
if it isnt i want it to use that part of the code again and check in my database again if it is unique,
if it is unique it should write to my database.
i have all the code i need for checking and writing into the database, i just have no idea how to make it loop back to the start.
Help is appreciated!
Thanks a lot in advance!
Don't bother checking at first. Instead put a unique constraint on the column, that way the insert will fail if the RCode isn't unique. Then you can handle that error/exception and try another hash. The probability of a collision is low so in this case you probably aren't going to be hammering the database.
Typical example for do-while loop.
Some PHP-pseudocode:
do {
$rcode = md5(uniqid(rand(), true));
$res = mysql_result(mysql_query("SELECT COUNT(*) FROM records WHERE rcode='$rcode'"));
} while ($res[0] > 0);
mysql_query("INSERT INTO records (rcode) VALUES ('$rcode')");
$found = false;
while (! $found) {
//try..
if (...unique...) {
$found = true;
}
}
Going to the start is as easy as implementing a while loop. Heck, you could even use goto (kidding!).
But I don't understand why you don't want to use auto_increment.
You can use the "loop forever then break out on success" method:
while (true) {
$RCode = ...;
if ($RCode does not exist in db) {
break;
}
}
write to the db
Edit: Or better, make sure the field has a unique constraint on it, then test for uniqueness by checking for failure of an insert:
while (true) {
$RCode = ...
try to insert RCode
if (no failure) {
break;
}
}
This will be more resilient to concurrent hits.
This answer is a little different to what you asked but it solves the problem of a unique ID in a different way that may be better to use depending on your application.
To be honest I like to use variables such as date and time combined with another variable such as an IP address for this type of thing, you can then be very certain that your ID will be unique because the date and time will not reoccur and in the event there are 2 requests in the same second the IP address of the user is completely unique at this time also. Plus no having to check with the database. An example would be
$idstring = date('Ymdhis');
$ipstring = $_SERVER['REMOTE_HOST'];
$hashme = $idstring.$ipstring;
$idhash = md5($hashme);
I hope this is helpful.
As you hash uniqid() returned value using md5() (the same goes for any other hashing algorithm), possibility of getting not unique string is extremely low.
In my opinion, its so low that checking for that string to be unique would be overkill.
All you need to do is insert value in the database. It will be unique!
Edit:
You should use uniqid(null, true) to get 23 chars long, unique string. If that is what you need - unique string.
you can just run MD5 on the timestamp...
$uniqueid = md5(time());
First, don't md5 uniqueid. uniqueid is good enough and it does not have the overhead of md5 if used by itself.
Second, you are far better off obscuring an automatically incrementing number than using a UUID or some equivalent.
But, if you must:
do {
$rcode = uniqid(rand(), true);
$res = mysql_query("SELECT COUNT(*) FROM records WHERE rcode='$rcode'");
} while ($res && mysql_num_rows($res));
mysql_query("INSERT INTO records (rcode) VALUES ('$rcode')");
// OR!
// assuming unique index on rcode
do {
$rcode = uniqid(rand(), true);
} while (mysql_query("INSERT INTO records (rcode) VALUES ('$rcode')"););
Related
In a table, the primary field is a Char(12) field called ribiid, whose format is RB##########,
It needs to auto-increment it self, and for that i have prepared the following code:
function getid() {
global $connection;
$idquery = "SELECT ribiid FROM systems ORDER BY ribiid DESC LIMIT 1";
$idsave = mysqli_query($connection, $idquery);
$idresult = mysqli_fetch_assoc($idsave);
$idalpha = substr($idresult['ribiid'], 0, 2);
$idnumeric = substr($idresult, 2);
$newidnumeric = $idnumeric + 1;
$newid = $idalpha . $newidnumeric;
return $newid;
}
Now for testing I manually entered a row in cmd with id = RB0000000000, the next entry that I submit through my webpage using php, should have been RB0000000001, but it is coming RB1.
How can I fix this, this is my first web database. Thanks
Your problem is that when adding 1 to $idnumeric PHP needs to treat it as a number. Leading zeroes in numbers do not make sense, so they are discarded.
To keep the zeroes you can use sprintf format the resulting (incremented) number:
$newid = sprintf("%s%010d", $idalpha, $newidnumeric);
However, using code like this is not a really good idea
There's an issue with this code though: it's subject to a race condition. Consider what could happen if two instances of the script run in parallel:
Instance A Instance B
T |
i | Reads ribiid RB..001 Reads ribiid RB..001
m | Generates next id RB..002 Generates next id RB..002
e v Writes RB..002 to DB
Writes RB..002 to DB => OOPS
As you see this situation will result in instance B failing to insert a record due to the use of a duplicate primary key. To solve this problem you need to eliminate the race condition, which you could do in one of several ways:
Use an AUTO_INCREMENT column for the PK instead of manually inserting values. Although this means you can no longer have the "RB" prefix as part of the key, you can move it to a different column and have the PK be a combination of these two columns.
LOCK TABLES ribiid while the insertion is taking place (note that the lock needs to cover all of the process, not just the getid function). Locking tables is something you normally want to avoid, but if inserts are not frequent it's a usable practical solution.
You could try something like this:
$newid = $idalpha . str_pad($newidnumeric, 10, '0', STR_PAD_LEFT);
This will add zeros to reach the ten chars.
You can padd the numeric string again using the following function:
function pad_number($number, $pad=10){
$pad_zero = $pad - strlen($number.'');
$nstr = '';
for($i =0; $i< $pad_zero; $i++){
$nstr .="0";
}
$nstr .= $number;
return $nstr;
}
You can use in your code this function as:
$newid = $idalpha . pad_number($newidnumeric);
I wrote a function which makes a random id makeid(); Just to ensure the id is unique I have a SQL statement which checks if the id already exists.
$does_id_exist = mysql_query("SELECT COUNT(*) AS count FROM signups WHERE affid='$affid'");
if(mysql_num_rows($does_id_exist) == 1)
{
#loop function and perform query again
}
else
{
#insert record
}
So I'm having trouble with looping the function. How do I loop my function makeid() and perform the $does_id_exist check to ensure that each ID is unique.
--UPDATE-- Just to clarify- My code makes an id like YES#281E But before I INSERT this id into the users record. I just need to verify IF any other user already has this id. IF another user has this id that event must trigger my function to create a new id e.g. WOW!29E3 and again check the sql/query to ensure no other user has that id. Continue to loop if fails or end and INSERT if the id is available.
You can either just use a primary key on your database table, or something like this:
<?php
// the id to insert
$newId = null;
// populate with results from a SELECT `aff_id` FROM `table`
$currentIds = array();
// prepopulate
for( $i=0; $i<100000; $i++ )
{
$currentIds[] = "STRING_" + rand();
}
// generate at least one id
do
{
$newId = "STRING_" + rand();
}
// while the id is taken (cached in $currentIds)
while( in_array($newId, $currentIds) );
// when we get here, we have an id that's not taken.
echo $newId;
?>
Output:
STRING_905649971 (run time 95ms);
I'd definitely not recommend running the query repeatedly. Perhaps a final check before you insert, if your traffic volume is high enough.
Do not do COUNT(*), because you do not need to know how many rows is there (it should be 0 or 1 as you need Id unique), so even DB finds your row it will still be checking for the whole table to count. You really care if you got 1 row, so just select for row with that ID and this sufficient. You should also avoid using rand() - this does not help as you see and you cannot predict how many loops you can do before you find "free slot". use something predictable, like date prefix, or prefix incremented each day. anything that would help you narrow the data set. But for now (pseudocode!):
$id = null;
while( $id == null ) {
$newId = 'prefix' . rand();
mysql_query("SELECT `affid` FROM `signups` WHERE `affid`='${newId}'");
if( mysql_num_rows() == 0) {
$id = newId;
break;
}
}
Ensure you got DB indexed, to speed things up.
EDIT: I do agree that any cache would be useful to speed things up (you can add it easily yourself based on #Josh example), still, I think this is fixing at wrong place. If possible rethink the way you generate your ID. It does not really need to be auto increment, but something more predictable than rand() would help you. If your ID does not need to be easily memorable and it is not any security concern to have them sequential, maybe use numbers with other base than 10 (i.e. using 26 would use all digits + letters so you'd end with PREFIX-AX3TK, so string as you want, and at the same time you would easily be able to quickly generate next Id
I'm trying to create a function that, when run, creates numerical ID, checks to see if that ID exists in the DB and if it does exists then create another ID and check again until it has a unique ID. I'm stuck on how to loop in the functions withing a function
function createUniqueID() {
function buildUnique() {
$uniqueID = rand(100000000000,999999999999);
return $uniqueID;
}
function compareWithDB($uniqueID) {
$s = "SELECT id FROM table WHERE id='{$uniqueID}'";
$r = mysql_query($s);
return $r;
}
function countDBRows($r) {
if(mysql_num_rows($r) >0){
$f = false; // found
} else{
$f = true;
}
}
$uniqueID = buildUnique();
$r = compareWithDB($uniqueID);
$f = countDBRows($r);
if (!$f) {
$uniqueID = 'nope';
}
return $uniqueID;
}
You're much better off to call MySQL's UUID() function, and store & return the value of that, unless the value absolutely must be numeric.
SELECT UUID();
If you want a unique number, then just use AUTO_INCREMENT
If you want a unique random number (why?) create a unique index on the ID column and keep trying until you get no errors. This is better for concurrency: multiple concurrent calls can have the same number and pass the test. Better still, let the DB engine do it with RAND...
Theres another way too,
You can use the sha1() function in conjuction with an static variable as shown:
function GetUniqueID() {
static $salt=1;
$id = sha1("somestring".(string)$salt);
$salt++;
return $id;
}
Here $salt being an static one retains its value between all calls incrementing ensuring unique ID's (hash of SHA1).for more security "somestring" can be made randomized also .
Might I suggest the much easier and more efficient uniqid function which will do this for you. It generates guaranteed unique IDs based on the timestamp in milliseconds. The generated Id is 13 digits long unless you decide to add a prefix or use extra 'entropy' (more uniqueness).
Edit: Note that this is both numbers and letters returned by the function.
http://php.net/manual/en/function.uniqid.php
Edit 2:
Using your method here is how to nest the loops...
while (true) {
$uniqueID = buildUnique();
$r = compareWithDB($uniqueID)
if (countDBRows($r)) { break; }
}
Your unique ID is then stored in $uniqueID. I do however discourage this because it's bulky and inefficient... but there you go! :)
I'm answering my own question because I have found a solution of creating a big number that (likely) will never duplicate. I use three random 2-digit variables and three different date syntax.
Putting them together makes it clear that they will never duplicate unless someone happens to post during the same year, week and with the same number of seconds in the minute an also have 3 different random number all be the same.
I think the chances of this happening would be in the millions and if there are millions of posts happening then i'm sure I will have more resources to solve this issue.
$rand1 = rand(10,99);
$rand2 = rand(10,99);
$rand3 = rand(10,99);
$date1 = date("s"); // seconds
$date2 = date("y"); // 2 digit year
$date3 = date("W"); // week number (out of 52)
$uniqueID = $date1.$rand1.$date2.$rand2.$date3.$rand3;
return $uniqueID;
I am trying to implement a check in my PHP code, that checks if there is a duplicate uid in the database, and if so, to assign a new uid, and check again, but I am having trouble nailing the logic, here is what I have thus far,
function check($uid){
$sql = mysql_query("SELECT * FROM table WHERE uid='$uid'");
$pre = mysql_num_rows($sql);
if($pre >= 1){
return false;
}else{
return true;
}
}
And then using that function I thought of using a while loop to continue looping through until it evaluates to true
$pre_check = check($uid);
while($pre_check == false){
//having trouble figuring out what should go here
}
So basically, once I have a usable uid, write everything to the database, else keep generating new ones and checking them till it finds one that is not already in use.
It is probably really simple, but for some reason I am having trouble with it.
Thanx in advance!
$uid = 100; // pick some other value you want to start with or have stored based on the last successful insert.
while($pre_check == false){
$pre_check = check(++$uid);
}
Of course ths is exactly what 'auto incrementing' primary keys are useful for. Are you aware of 'auto incrementing' primary keys in mysql?
EDIT
In light of your comment regarding maintaining someone else's code that uses the random function like that (ewwwww)... I would use the method I suggest above and store the last inserted id somewhere you can read it again for the next user. This will allow you to "fill-in-the-blanks" for the uids that are missing. So, if for example you have uids 1, 2, 5, 9, 40, 100... you can start with $uid = 1; Your while loop will return once you get to 3. Now you store the 3 and create the new record. Next time, you start with $uid = 3; and so on. Eventually you will have all numbers filled in.
It is also important to realize that you will need to do the inserts by either locking the tables for WRITES. You don't want to get into a race condition where two different users are given the same uid because they are both searching for an available uid at the same time.
Indeed the best is to use autoincrement ids, but if you don't have the choice, you can do a reccursive function like that:
function find_uid() {
$new_uid = rand(1000000000, 9999999999);
$sql = mysql_query("SELECT COUNT(*) AS 'nb' WHERE uid=".$new_uid.";");
$row = mysql_fetch_assoc();
$pre = $row['nb'];
return ($pre >= 1 ? find_uid() : $new_uid);
}
COUNT(*) should be more performant because the count is made by MySQL and not php.
By the way, if you need a new uid shouldn't the condition be ($pre > 0) instead of ($pre > 1) ?
I'm using PHP and MySQL and
I have a table with 3 fields ((ID, Username, PID)).
I want the PID field to contain strings of 8 unique characters.
My solution is to generate the random string in PHP and check if it exists. If it exists then it will generate another string.
Is there any better solution that will save processing time, like a MySQL trigger or something like that?
This will give you a random 8 character string:
substr(str_pad(dechex(mt_rand()), 8, '0', STR_PAD_LEFT), -8);
Found here: http://www.richardlord.net/blog/php-password-security
Or if the username field is unique you could also use:
substr(md5('username value'), 0, 8);
Though it's extremely unlikely, particularly for the md5, neither case guarantees a unique string, so I would probably do something like this:
// Handle user registration or whatever...
function generatePID($sUsername) {
return substr(md5($sUsername), 0, 8);
}
$bUnique = false;
$iAttempts = 0;
while (!$bUnique && $iAttempts < 10) {
$aCheck = $oDB->findByPID(generatePID("username value")); // Query the database for a PID matching whats generated
if (!$aCheck) { // If nothing is found, exit the loop
$bUnique = true;
} else {
$iAttempts++;
}
}
// Save PID and such...
... which would probably only yield 1 'check' query, maybe 2 in unique cases, and would ensure a unique string.
Do the characters need to be random? Or just unique? If they only need to be unique, you could use a timestamp. Basing the value on time will ensure a uniqueness.
If you go another route, you'll have to check your generated value against the database until you end up with a unique value.
Why not do this the correct way and use UUIDs (aka GUIDs), which are always unique, no need to check if they are or not. It may be 36 chars, but you get the benefit of storing them as HEX which saves disk space and increase speed over standard CHAR data.
You can read the comments on the PHP doc for functions that do this.
You can create 8 chars unique string in Mysql in such a way
CAST(MD5(RAND()) as CHAR(8))
My solution is to generate the random string in PHP and check if it exists. If it exists then it will generate another string.
This is the wrong way to do it. The web server will run multiple instances of your code concurrently, and sooner or later, two instances will store the same PID in your database.
The correct way to solve this problem is to make the PID column UNIQUE, and don't bother with any pre-checks. Just run the INSERT query, and check the result.
If the result is a 1062 (ER_DUP_ENTRY) error, generate a new PID and try again.
Any other database error should be dealt with like you normally would.
Perhaps something like this (untested):
<?php
/* $link = MySQLi connection */
if (!($stmt = mysqli_prepare ('INSERT `t` (`ID`, `Username`, `PID`) VALUES (?, ?, ?)'))) {
/* Prepare error */
}
if (!mysqli_bind_param ('iss', $id, $user, $pid) {
/* Bind error */
}
$e = 0;
for ($i = 0; $i < 10; $i++) {
$pid = /* generate random string */;
if (mysqli_stmt_execute ($stmt))
break; /* success */
$e = mysqli_stmt_errno ($stmt);
if ($e !== 1062)
break; /* other error */
}
mysqli_stmt_close ($stmt);
if ($e) {
if ($e === 1062) {
/* Failed to generate unique PID */
} else {
/* Other database error */
}
} else {
/* success */
}
If you're set on 8 characters for the PID value then you'll need something to generate the string and check that it doesn't already exist.
$alphabet = range('A','Z');
// get all the PIDs from the database
$sql = "select PID from mytable";
// save those all to an array
$pid_array = results of query saved to array
shuffle($alphabet);
$pid_offer = array_slice($alphabet,0,8);
while(in_array($pid_offer, $pid_array)){
shuffle($alphabet);
$pid_offer = array_slice($alphabet,0,8);
}
// found uniuqe $pid_offer...
race conditions still exist.
If the string doesn't need to be random, then use the ID value, which is probably an auto-increment integer and start the count for that at 10000000.
Then just do a simple A=1, B=2, C=3 etc replacement on the digits in that number to generate your string.
Your mileage may vary.
--Mark