I have an mobile app that request a key in my server, The key structure contains 7 characters as follows:
# + [0-9] + [0-9] + [0-9] + [A-Z] + [A-Z] + [0-9]
#876EU8, #668KI2 .......
Whereas the key initially has seven characters, in this case three numbers, two letters and one number, doing the math this gives a maximum of 676,000 keys.
To gerate this keys I'm using this code in PHP:
function generateRandomString($length = 2) {
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
$randomKeyNumber = rand(100,999);
$randomKeyLetter = generateRandomString();
$randomKeyLast = rand(0,9);
$randomKey = "#".$randomKeyNumber.$randomKeyLetter.$randomKeyLast;//Returns a key like #876TG9
The next code check if the key exists inside the database, If exists he random another key, if not he insert the key in database and return this key to my app
This code works perfectly, but assuming the system has already generated a total of 650,000 keys, in the case of this code it would always generate the same keys, and the likelihood of it generate a key that does not exist yet is very small.
How can I solve this problem and avoid future problems? (There is no problem in creating the keys in an orderly manner, eg 000AA0, 000AA1, 000AA2, 000AA3 .... 999ZZ9)
What you can do is make a PDO::query() to issue a SELECT COUNT(*) or simply a SELECT * statement with all the keys you already have added, and then use PDOStatement::fetchColumn() to retrieve the number of rows that will be returned (i.e. in this case, all of them)
This is a manual example
<?php
$sql = "SELECT COUNT(*) FROM Keys";
if ($res = $conn->query($sql)) {
/* Check the number of rows that match the SELECT statement */
if ($res->fetchColumn() > 0) {
/* Issue the real SELECT statement and work with the results */
$sql = "SELECT name FROM fruit WHERE calories > 100";
foreach ($conn->query($sql) as $row) {
print "Name: " . $row['NAME'] . "\n";
}
}
/* No rows matched -- do something else */
else {
print "No rows matched the query.";
}
}
$res = null;
$conn = null;
?>
This is the code you need for your case:
<?php
$sql = "SELECT * From Keys";
if ($res = $conn->query($sql)) {
/* Check the number of rows that match the SELECT statement */
if ($res->fetchColumn() > 0) {
/* and then you get the id of the last one on the list, and to that one you add 1 */
$last_id = $conn->lastInsertId();
$new_id = $last_id + 1;
/* then you insert that in some place inside the key itself, that way you don't need to worry than two keys can be equal */
}
else {
/* No rows matched, just create a key and add to the database here */
}
}
¿>
Alternatively you can make a query SELECT statement combined with the countRows in PDO, it doesn't work all the times in the portable apps and/or databases, but like we don't know more about your app we can't know if this is gonna work.
PS. Don't use rand(). Use mt_rand() instead. It is more efficient with the resources of the server ;)
Related
I have a PHP project in which i want to create and assign random User IDs to my customers when they sign-up in to our company's second website. It must be random generated user Ids that must not duplicate in our MySQL Database. User IDs should be like XYZ654986, HPR654986, WRU934765, SYW365824.
How can I create , check and insert user IDs like these ?
First of all, while random IDs for public services (like YouTube Video IDs) in the URL are useful, internally you shouldn't use random IDs. An ID which is used only backend could be made with AutoIncrement.
Nevertheless, you could have specific reasons to use a random ID.
First, you need to create a random code.
This creates a random code with the length 10 (you can change the length by changing $i<10):
$char = "0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z";
$chars = explode ( ",", $char );
$code = "";
for ($i=0; $i<10; $i++) {
$random = rand(0, (count($chars)-1));
$code .= $chars[$random];
}
Then, you need to check whether the code is already used or not.
If you have the codes in your MySQL database, you can use this:
$EveryID = array();
$statement = $pdo->prepare("SELECT ID FROM MyDatabase");
$statement->execute();
while($row = $statement->fetch()) {
array_push($EveryID, $row["ID"]);
}
$IDexists = false;
for ($i=0; $iy<count($EveryID), $i++) {
if ($EveryID[$i] == $code) {
$IDexists = true;
break;
}
}
And if $IDexists is true, you need to do the same (generate new code, etc.). I would do this with a while loop.
If $IDexists is false, you have a new unused code in $code. Then, you need to insert this code with other user information (e.g. name).
Here the full code:
$IDexists = true;
while ($IDexists == true) {
$char = "0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z";
$chars = explode ( ",", $char );
$code = "";
for ($i=0; $i<10; $i++) {
$random = rand(0, (count($chars)-1));
$code .= $chars[$random];
}
$EveryID = array();
$statement = $pdo->prepare("SELECT ID FROM MyDatabase");
$statement->execute();
while($row = $statement->fetch()) {
array_push($EveryID, $row["ID"]);
}
$IDexists = false;
for ($i=0; $iy<count($EveryID), $i++) {
if ($EveryID[$i] == $code) {
$IDexists = true;
break;
}
}
}
$statement = $pdo->prepare("INSERT INTO MyDatabase (ID, name, something) VALUES (:ID, :name, :sth)");
$statement->execute(array("ID" => $code, "Name" => $name, "sth" => $Something));
Requirements for this method are that you've made a PDO connection with your database at the beginning of your .php file. If you haven't made that, just google "php MySQL PDO" and look at a tutorial.
At last, I just want to say: If you use the IDs internally (that means the IDs aren't in the URL to the user's site), I'd recommend to make an Auto-Increment ID (that means, the first user has the ID 1, the second 2, etc.). You can set the ID in PhpMyAdmin as primary and check auto-Incrediment to do this (there are tutorials for this, too). That's way easier and for most use-cases more practical (but only if the IDs aren't in the URL on any place on your site).
Edit: Instead of
while($row = $statement->fetch()) {
array_push($EveryID, $row["ID"]);
}
you can use
$EveryID = $statement->fetchAll();
Hi I'm currently querying from a database base user ids for a contest, However I want to avoid choosing duplicates in my results_array, this function getrandomspecies receives a array_result, this array results iterates 7 times. How test that I don't put duplicates in my results_array? I have gotten several duplicates.
function getrandomspecies($array_result){
//database connection
$dbn = adodbConnect();
foreach($array_result as $possible){
//query the results
$querys= "select * from taxonomic_units where tsn = $possible";
$resultss = $dbn -> Execute($querys);
while($rowss=$resultss->FetchRow()){
$id = $rowss['tsn']; //there ID
$ranksss = $rowss['rank_id']; //ranking id, I choose 220 and 230
if($ranksss == 220 || $ranksss == 230){
$prelimary_array[] = $id;
}
}
//grab random index
$index = array_rand($prelimary_array,1);
//put result id into a variable
$newspecies = $prelimary_array[$index];
//put that variable in an array
$results_array[] = $newspecies; //there is 7 newspecies/winners at the end, I dont want duplicates
}
return $results_array;
}
MySQL should be the following :
select distinct tsn, rank_id from taxonomic_units where tsn = $possible
But you should ideally use prepared statements.
what about this? You may do it with one query:
$querys= "select DISTINCT tsn from taxonomic_units where tsn IN (".implode(",",$array_result).") AND rank_id IN (220,230) ORDER BY RAND() LIMIT 7 ";
Loop your result array and if it does not exists add it. If you end up with less than 7, do your big loop again.
replace this line :
$results_array[] = $newspecies;
by:
$loop_1_more_time=0;
if (isset($results_array)){
foreach($results_array as $result){
if ($result == $new_specie){
$loop_1_more_time=1;
}
}
}
if ($loop_1_more_time == 0){
$results_array[] = $newspecies;
}
//there, if $loop_1_more_time equals 1, start again. To start again and be sure you have seven instead of 6 or less, You could replace your big first "foreach" loop with a "for" loop that depends of the count() of the $array_result, and the $array_result would be array_result[$i] instead of $possible. $i would start at 0 and increment at each end of loop. It would not be incremented if $loop_1_more_time==1;.
Example :
for ($i = 0; $i < count($array_result); $i++) {
//stuff
//if ($loop_1_more_time=1;) { $i--; }
}
Why don't you try shuffling the array, and then picking the first X numbers?
That way, rather than having to check the results array for duplicates, it will never come up in the first place
Hi I am creating a system that processes and ID and a UID, The UID we are generating randomly but I am a little stuck, I need to always generate a UID that does not currently exist in the db as the field is a unique field used on the front end so as not to expose the real ID.
So to recap, I am trying to generate a unique id that does not currently exist in the DB the part I haven't got working is the cross checking in the db so it sometimes will give a number that already exists in the db even though it shouldn't thanks in advance.
This is my code so far:
function uniqueID($table)
{
$db = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
$possible = '1234567890';
$code = '';
$characters = mt_rand(7,14);
$i = 0;
while($i < $characters)
{
$code .= substr($possible, mt_rand(0, strlen($possible)-1), 1);
$i++;
}
$result = $db->query('
SELECT uniqueID
FROM '.$table.'
WHERE uniqueID = "'.$code.'"
LIMIT 1
');
$totalRows = $result->num_rows;
if(!$result)
{
return $db->error;
}
else
{
if($totalRows > 0)
{
return uniqueID($table);
}
else
{
return $code;
}
}
}
http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_uuid
To generate unic UID you can use time, i think it was a very small chanse that records will be added in the same second, with two random data.
write some function which return it to you like that
function generate_uid(){
return md5(mktime()."-".rand()."-".rand());
}
In PHP there's a function called uniqid()
http://php.net/manual/en/function.uniqid.php
I could talk about generating ids, like the others did, but this is not your question.
Your query seems fine. If it returns 0 rows but you seem to find the code in the database, then most likely it only looks the same, but actually isn't. It could be padded by whitespace.
One way to solve this is by selecting the last row of your user database and have your script to check for the id field (you can achieve this by performing a select ordering by ID in descendent mode) then you can use that info for randomize numbers greater than that ID.
EDIT
$result = $db->query('
SELECT uniqueID
FROM '.$table.'
');
$already_in_database = array();
while ($row = mysql_fetch_array($result)){
$already_in_database[] = $row['UID'];
}
$new = rand(0,$some_max_value);
while(in_array($new,$already_in_database)){
$new = rand(0,$some_max_value);
}
I figured out what the problem was already, As I mentioned to everyone the code generation was not the issue! The issue was that the cross check was not working correctly. So all I did was removed this loop
while($i < $characters)
{
$code .= substr($possible, mt_rand(0, strlen($possible)-1), 1);
$i++;
}
As this was causing my unique ID to end up wrong.
I have a game script thing set up, and when it creates a new character I want it to find an empty address for that players house.
The two relevant table fields it inserts are 'city' and 'number'. The 'city' is a random number out of 10, and the 'number' can be 1-250.
What it needs to do though is make sure there's not already an entry with the 2 random numbers it finds in the 'HOUSES' table, and if there is, then change the numbers. Repeat until it finds an 'address' not in use, then insert it.
I have a method set up to do this, but I know it's shoddy- there's probably some more logical and easier way. Any ideas?
UPDATE
Here's my current code:
$found = 0;
while ($found == 0) {
$num = (rand()%250)+1; $city = (rand()%10)+1;
$sql_result2 = mysql_query("SELECT * FROM houses WHERE city='$city' AND number='$num'", $db);
if (mysql_num_rows($sql_result2) == 0) { $found = 1; }
}
You can either do this in PHP as you do or by using a MySQL trigger.
If you stick to the PHP way, then instead of generating a number every time, do something like this
$found = 0;
$cityarr = array();
$numberarr = array();
//create the cityarr
for($i=1; $i<=10;$i++)
$cityarr[] = i;
//create the numberarr
for($i=1; $i<=250;$i++)
$numberarr[] = i;
//shuffle the arrays
shuffle($cityarr);
shuffle($numberarr);
//iterate until you find n unused one
foreach($cityarr as $city) {
foreach($numberarr as $num) {
$sql_result2 = mysql_query("SELECT * FROM houses
WHERE city='$city' AND number='$num'", $db);
if (mysql_num_rows($sql_result2) == 0) {
$found = 1;
break;
}
}
if($found) break;
}
this way you don't check the same value more than once, and you still check randomly.
But you should really consider fetching all your records before the loops, so you only have one query. That would also increase the performance a lot.
like
$taken = array();
for($i=1; $i<=10;$i++)
$taken[i] = array();
$records = mysql_query("SELECT * FROM houses", $db);
while($rec = mysql_fetch_assoc($records)) {
$taken[$rec['city']][] = $rec['number'];
}
for($i=1; $i<=10;$i++)
$cityarr[] = i;
for($i=1; $i<=250;$i++)
$numberarr[] = i;
foreach($cityarr as $city) {
foreach($numberarr as $num) {
if(in_array($num, $taken[]) {
$cityNotTaken = $city;
$numberNotTaken = $number;
$found = 1;
break;
}
}
if($found) break;
}
echo 'City ' . $cityNotTaken . ' number ' . $numberNotTaken . ' is not taken!';
I would go with this method :-)
Doing it the way you say can cause problems when there is only a couple (or even 1 left). It could take ages for the script to find an empty house.
What I recommend doing is insert all 2500 records in the database (combo 1-10 with 1-250) and mark with it if it's empty or not (or create a combo table with user <> house) and match it on that.
With MySQL you can select a random entry from the database witch is empty within no-time!
Because it's only 2500 records, you can do ORDER BY RAND() LIMIT 1 to get a random row. I don't recommend this when you have much more records.
I have never had a need to do a random SELECT on a MySQL DB until this project I'm working on. After researching it seems the general populous says that using RAND() is a bad idea. I found an article that explains how to do another type of random select.
Basically, if I want to select five (5) random elements, I should do the following (I'm using the Kohana framework here)?
<?php
final class Offers extends Model
{
/**
* Loads a random set of offers.
*
* #param integer $limit
* #return array
*/
public function random_offers($limit = 5)
{
// Find the highest offer_id
$sql = '
SELECT MAX(offer_id) AS max_offer_id
FROM offers
';
$max_offer_id = DB::query(Database::SELECT, $sql)
->execute($this->_db)
->get('max_offer_id');
// Check to make sure we're not trying to load more offers
// than there really is...
if ($max_offer_id < $limit)
{
$limit = $max_offer_id;
}
$used = array();
$ids = '';
for ($i = 0; $i < $limit; )
{
$rand = mt_rand(1, $max_offer_id);
if (!isset($used[$rand]))
{
// Flag the ID as used
$used[$rand] = TRUE;
// Set the ID
if ($i > 0) $ids .= ',';
$ids .= $rand;
++$i;
}
}
$sql = '
SELECT offer_id, offer_name
FROM offers
WHERE offer_id IN(:ids)
';
$offers = DB::query(Database::SELECT, $sql)
->param(':ids', $ids)
->as_object();
->execute($this->_db);
return $offers;
}
}
If not, what is a better solution?
That approach will work, as long as your offer_id's are sequential and all continuous - if you ever remove an offer, you might have gaps in the id's that would then be a problem.
I've read the same things about the MySQL rand() function on large table sets, but I would think you could do it faster by counting the table rows, then using PHP's built in rand(0, count) to generate a few index ID's you can grab in a SELECT. I suspect it would have the same affect but without all the performance concerns.