PHP PDO Unique Random Number Generator - php

Hello so first of all please consider this question as a newbie one because I can just set an ID field and add zerofill so it would look like 000001, 000002 and so fort. But what I did is wrong, and the system is already big so please consider my question. I have a table named accounts which has an id field and sponsorID field. Now what I did looks like this (btw I am using slim framework):
$db = new db();
$sponsorIDrandom = mt_rand(100000, 999999);
$bindCheck = array(
":sponsorID" => $sponsorIDrandom
);
$sponsorIDChecker = $db->select("accounts", "sponsorID = :sponsorID", $bindCheck);
$generate_SID = null;
do {
$generate_SID = mt_rand(100000, 999999);
} while (in_array($generate_SID, array_column($sponsorIDChecker, 'sponsorID')));
$db->insert("accounts", array(
"sponsorID" => $generate_SID
));
The code above will check if a number already exist in the accounts table and if there is an existing, it will generate a random number again until it becomes unique or non-existing in the accounts table. I made the sponsorID field unique so that it won't accept duplicate values.
Now the problem is the code I posted. I thought it would let the $generate_SID be unique because I used the in_array function so it would check if a value already exist in the array and do generate a number again until it is unique but I did receive luckily an error that it tried to insert a random number that already exists and it didn't generate a new one.
Can anyone tell me if there's a solution for this? Or should I re-modify the code above so it would not enter already existing sponsorID? Thank you in advance.

From what i understood, you try to insert a unique id into a table but the generator only runs once and or it tells you that the number already exists.
I've never used slim but it seems in your code you try to do a SELECT of a single record, because you generate a random number and then ask for this number to the database:
$sponsorIDrandom = mt_rand(100000, 999999);
$bindCheck = array(
":sponsorID" => $sponsorIDrandom
);
$sponsorIDChecker = $db->select("accounts", "sponsorID = :sponsorID", $bindCheck);
This only returns one or none rows if as you say the sponsorID is UNIQUE.
And then you try to generate another random number and check if is not repeated based on this single (or null) record.
$generate_SID = null;
do {
$generate_SID = mt_rand(100000, 999999);
} while (in_array($generate_SID, array_column($sponsorIDChecker, 'sponsorID')));
this loop only executes once because the probability of this second random number to be inside this record (if there is a record at all) are almost none and if the database are as big as you says, then the probability for collisions are too high.
for this code to work you need to load every record or ask for the newly generated number if it exists in the database every time it is generated, both alternatives are not recommended but since the databse is already (almost) full.
$sponsorIDChecker = $db->select(...); //use the equivalent of "SELECT sponsorID from accounts" without the WHERE clause, is better to ask for a single column.
$generate_SID = null;
do {
$generate_SID = mt_rand(100000, 999999);
} while (in_array($generate_SID, ...)); //here you put the result of the query above.
$db->insert("accounts", array(
"sponsorID" => $generate_SID
));
Now, something that may be of help: if you set the sponsorID as a zerofill in the databse as you said
I can just set an ID field and add zerofill so it would look like 000001, 000002 and so fort.
then you can lower the min value of mt_rand to 0 and you gain 100000 more IDs to try.

Related

PHP automatically removes the zeros that i need in an identification string

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);

Check if username exists, if so, increment by one

When a username is inserted into my database, such as:
John_Smith
I need to check if there is already a John_Smith present. If so, increment it by 1 to become John_Smith_1
So if the following usernames already exist:
John_Smith
John_Smith_1
John_Smith_2
John_Smith_3
....up to John_Smith_10
I need the next John_Smith inserted to be incremented to John_Smith_11.
So far, I have searched and come up with this:
$preferredname= "John_Smith"
//check for duplicate user names
$duplicate= check_for_duplicate_username($preferredname);
//if duplicate, increment the preferredname
if ($duplicate)
{
$parts = explode("_",$preferredname);
if (isset($parts[1]))
$preferredname = $parts[0]."_".$parts[1]."_".($parts[2]+1);
else $preferredname = $parts[0]."_".$parts[1]."_1";
}
This, I believe would work for only the first matching usernames. My problem is checking the database for the version of the name with the highest number.. This is my sql:
function check_for_duplicate_username($name)
{
// check if username already exists
$sql = "SELECT * FROM users WHERE user_username=$name";
//then return duplicates
You can use this query:
$sql = 'SELECT * FROM users WHERE user_username LIKE ' . $name . '_%';
the above will query for rows in which user_username have value similar to John_Smith_* ( where * is any valid character including number, which you have to check later )
you can use this php statement for getting user's suffix number:
preg_match( '/([0-9]+)$/', 'John_smith_10', $matches );
$user_number = $matches[0];
While this may not be the best solution in the grand scheme of things, here is an answer that addresses what you were specifically trying to do. The following query returns the number of users with either $name or $name_123 SELECT COUNT(*) FROM users WHERE user_username REGEXP '$name[_0-9]*$'; So if the value is 0 you can simply use $name by itself, otherwise you can use $name."_". + the number returned by the query.
But as many people have mentioned, prly not the best idea to autoassign usernames (very web 1.0 :P ). I'd recommend email addresses. Another option is to use whatever userid the social app uses along with another field identifying the social app (if you have multiple), and then using an autoincremented id field as the unique primary key..
I had a similar problem I was trying to solve today and did it like this. In my case I needed to store/create a unique directory name based on the users first initial/last name:
$username = "bjones";
$usersql = "SELECT * FROM users WHERE username LIKE '$username%'";
$usercnt = mysqli_num_rows(mysqli_query($con,$usersql));
// If bjones, bjones1, bjones2, etc already exists
if ($usercnt >= 1) {
$num = ++$usercnt; // Increment $usercnt by 1
$username = $username . $num; // Add number to username
}
Assuming bjones, bjones1, bjones2 already exists, I would end up with bjones3.

How to loop my function based on query result

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

MongoDB _id Field Auto Generation?

I am trying to setup mongodb to test out its speed and am running into a issue with _id duplication. I am not setting the is, I am letting mongodb do as I don't care. I ahve the following php code:
<?php
$mongo = new Mongo();
$db = $mongo->selectDB("scrap_fighters");
$collection = $db->selectCollection('scores');
$data = array
(
'user_id' => 1,
'name' => 'John Doe',
'score' => 120
);
$start = microtime(true);
for($x=0; $x < 1000; $x++)
{
$data['unqiue'] = microtime();
$result = $collection->insert($data, array('safe' => true));
}
?>
What does mongodb use to generate thier "unique" ids? I even tried replacing unique with:
$data['unqiue'] = rand(1, 1000000);
To be 100% sure it was working but it still failed after the first write. can I not enter records with the same data without specific generation a unique id myself?
MongoDB uses _id as primary key, and as such it has to be unique. Since you don't specify it, it will automatically generated. It consists of a microsecond timestamp and a hash based on the host, so even there are multiple hosts inserting simultaneously the probabily of collision is extremely low.
What is this unique field you are using? If you wanted it to be a primary key just don't set it.
About failing on duplicates: the only reason I can think of this happening is that you previously set up an index on this collection which requires some field (or combination of fields) to be unique. If not needed, remove it. If it's valid (eg: the user_id has to be unique) then insert unique records.

Checking mySQL db for duplicate uid

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) ?

Categories