I have a set of questions with unique IDs in a MySQL database.
Users also have a unique ID and are to answer these questions and their answers are saved in the database.
Now, I want users to get 5 non-repeating uniquely and randomly picked questions from the pool of available ones (let's say 50) based on users ID. So when a user with id 10 starts answering his questions, but stops and wants to return later to the same page, he will get the same questions as before. A user with id 11 will get a different random set of questions, but it will always be the same for him and different from all other users.
I found that random.org can generate exactly what I need with their sequence generator that generates a random sequence of numbers based on provided ID:
https://www.random.org/sequences/?min=1&max=50&col=1&format=plain&rnd=id.10
But I would like the generation to be done locally instead of relying random.org API.
So, I need to generate 'X' unique random integers, within specified range 'Y' that are generated based on supplied integer 'Z'. I should be able to call a function with 'Z' as parameter and receive back the same 'X' integers every time.
I need to know how to replicate this generation with PHP code or at least a push or hint in a direction of a PHP function, pseudo-code or code snippet that will allow me to do it myself.
Thank you in advance!
Why reinvent the wheel
mt_srand(44);
for ($i=0; $i < 10; $i++) echo mt_rand(). "\n";
echo "\n\n";
mt_srand(44);
for ($i=0; $i < 10; $i++) echo mt_rand(). "\n";
result
362278652
928876241
1914830862
68235862
1599103261
790008503
1366233414
1758526812
771614145
1520717825
362278652
928876241
1914830862
68235862
1599103261
790008503
1366233414
1758526812
771614145
1520717825
Generate your random numbers at the beginning and save it in a session. That way the random numbers for that user is always known and you can know what id of question you should go back to by looking it up in the session.
Cheers
you can get random $w array values. try this code as example and change with your logic.
$w = array('0'=>11,'1'=>22,'2'=>44,'3'=>55,'4'=>66,'5'=>88);
$str = '';
for($i=0;$i<5;$i++) {
$str.= $w[rand(0,5)];
}
As this article suggests, you could use a non-repeating pseudo random number generator. Only problem would be to generate a primnumber that is atleast 2x as big as the upper-bound for IDs and satisfies the condition p = 3 in the ring Z4. Though there should be big-enough primnumbers matching the conditions on the net for free use.
Due to my lack of experience with PHP i can only provide pseudocode though.
int[] generateUniqueRands(int id , int ct)
int[] res
const int prim//the primnumber described above
for int i in [0 , ct[
res[i] = ((id + i) * (id + i)) % prim
return res
Note that this algorithm basically works like a window:
id = x set = [a , b , c , d]
id = x + 1 set = [b , c , d , e]
...
If you wish to avoid this kind of behavior just generate a unique random-number from the id first (can be achieved in the same way the set of random numbers is generated).
When the user with ID 10 opens the page for the first time, use rand() to generate random numbers then store them into a cell in the users table in database. So the user with id 10 has the rand() numbers stored.
For example the users table has id, rand_questions.
Check if the rand_questions is empty then update with the new random numbers generated, else you get the numbers from the database.
Related
I'm using PHP 7 with Phalcon PHP and I'm trying to create a method to generate a booking number. Here is my current method :
public function generateNumber($company_code) {
// Build the prefix : COMPANY20190820
$prefix = $company_code . date('Ymd');
// It's like SELECT count(*) FROM bookings WHERE number LIKE 'COMPANY20190820%'
$counter = Bookings::count(array(
"number LIKE :number:",
"bind" => array('number' => $prefix.'%')
));
// Concat prefix with bookings counter with str_pad
// COMPANY20190820 + 005 (if 4 bookings in DB)
$booking_number = $prefix . str_pad($counter + 1, 3, 0, STR_PAD_LEFT);
// Return COMPANY20190820005
return $booking_number;
}
So I have a problem because sometime I have to delete 1 or multiple bookings so I can get :
COMPANY20190820001
COMPANY20190820002
COMPANY20190820005
COMPANY20190820006
COMPANY20190820007
And I need to add after the last in my DB so here 007, because I can get duplicated booking number if I count like that.
So how can I do to take the last and increment according the last booking number of the current day ?
You need to rethink what you want to do here as it will never work that way.
As I see it you have at least two options:
Use an auto-increment id and use that in combination with the prefix
Use a random fairly unique string (e.g. UUID4)
You should never manually try to get the current maximum id as that may and most likely will at some point result in race conditions and brittle code as a result of that.
So I found a solution, maybe there is a better way to do that but my function works now:
public function generateNumber($company_code) {
// Build the prefix : COMPANY20190820
$prefix = $company_code . date('Ymd');
// Get the last booking with the today prefix
// e.g : COMPANY20190820005
$last_booking = Bookings::maximum(array(
"column" => "number",
"number LIKE :number:",
"bind" => array('number' => $prefix.'%')
));
// Get the last number by removing the prefix (e.g 005)
$last_number = str_replace($prefix, "", $last_booking);
// trim left 0 if exist to get only the current number
// cast to in to increment my counter (e.g 5 + 1 = 6)
$counter = intval(ltrim($last_number, "0")) + 1;
// Concat prefix + counter with pad 006
$booking_number = $prefix . str_pad($counter, 3, 0, STR_PAD_LEFT);
// Return COMPANY20190820006
return $booking_number;
}
I reckon that the use case you describe does not justify the hassle of writing a custom sequence generator in PHP. Additionally, in a scenario where booking deletion is expected to happen, ID reusing feels more a bug than a feature, so your system should store a permanent counter to avoid reusing, making it less simple. Don't take me wrong, it can be done and it isn't rocket science, but it's time and energy you don't need to spend.
Your database engine surely has a native tool to generate autoincremented primary keys, with varying names and implementations (SQL Server has identity, Oracle has sequences and identity, MySQL has auto_increment...). Use that instead.
Keep internal data and user display separated. More specifically, don't use the latter to regenerate the former. Your COMPANY20190820007 example is trivial to compose from individual fields, either in PHP:
$booking_number = sprintf('%s%s%03d',
$company_code,
$booking_date->format('Ymd'),
$booking_id
);
... or in SQL:
-- This is MySQL dialect, other engines use their own variations
SELECT CONCAT(company_code, DATE_FORMAT(booking_date, '%Y%m%d'), LPAD(booking_id, 3, '0')) AS booking_number
FROM ...
You can (and probably should) save the resulting booking_number, but you cannot use it as source for further calculations. It's exactly the same case as dates: don't need to store dates in plain English in order to eventually display them to the end-user and you definitively don't want to parse English dates back to actual dates in order to do anything else beyond printing.
You also mention the possibility of generating long pure-digit identifiers, as Bookings.com does. There're many ways to do it and we can't know which one they use, but you may want to considering generating a numeric hash out of your auto-incremented PK via integer obfuscation.
you could split your database field in two parts, so you hold the prefix and the counter separately.
then, you simply select the highest counter for your desired prefix and increment that one.
if you can't change the table structure, you could alternatively order by the id descendingly and select the first. then you can extract its counter manually. keep in mind you should pad the numbers then, or you get #9 even if #10 exists.
if padding is not an option, you can direct the database to replace your prefix. that way, you can cast the remaining string to a number and let the database sort - this will cost some performance, though, so keep the amount of records low.
I'm building a fairly simple ticketing system in PHP where when a user purchases a ticket, they're emailed a PDF with a barcode attached, the barcode contains their order ID, which when scanned at the event, matches the ID of the order in the database and validates the ticket.
However, I don't want the order id to presented in plain text in the barcode, as somebody could simply create a barcode with one digit higher or lower than their own and then have a valid ticket. I've looked at MD5 and some other encryption algorithms, but they produce 64 bit alphanumeric strings, which aren't ideal, as there is a possibility of the need to type the number in rather than scanning it.
Ideally, an id would convert from something like 123 to 864374357, so it's still fairly simple for a human to type.
Any ideas on what the best method of creating a simple encryption like this would be?
Take a look at this question here, I think it will give you what you need!
Basically, just take the MD5 (or any algorithm) hash and then run a little extra processing on it to convert it to a numeric format, or limit its length.
You could generate a short unique number which is then stored in a separate column in the database. You could write a function like:
function generate_random_number($length) {
$random = '';
for($i = 0; $i < $length; $i++) {
$random .= rand(0, 9);
}
return $random;
}
And generate a unique number:
do {
$unique = generate_random_number(10);
$sql = "SELECT COUNT(*) FROM example_table WHERE unique_id = $unique";
$res = $conn->query($sql);
} while($res->fetchColumn() > 0);
echo $unique;
I am storing social security numbers in the database, but instead of storing whole numbers, I only store only 5 digits sequence. So, if SSN# is 123-12-1234, my database would store it #23121### or ####21234 or anything else, as long as it has a 5 digits in the row.
Therefore, when user enters whole SSN, I want the database to locate all matches.
So, I can do this :
SELECT * FROM user WHERE ssn like 123121234
But the query above would not work, since I have some masked characters in the SSN field (#23121###). Is there a good way of doing this?
Maybe a good way would be to use
SELECT * FROM user WHERE REPLACE (ssn, '#', '') like 123121234
Although there could be an issue - the query might return non-relevant matches since 5 numbers that I store in the DB could be anywhere in a sequence.
Any idea how to do a better search?
If the numbers are always in a sequential block, you can generate a very efficient query by just generating the 5 variations of the ssn that could be stored in the DB and search for all of them with an exact match. This query can also use indexes to speed things up.
SELECT *
FROM user
WHERE ssn IN ('12312####',
'#23121###',
'##31212##',
'###12123#',
'####21234');
I think you can do something like this:
Extract all possible 5-char combinations out of the queried SSN.
Make an IN() query on those numbers. I'm not sure though how many results you would get from this.
$n = 123121234;
$sequences = array();
for($i = 0; $i + 5 <= strlen($n); $i++) {
$sequences[] = substr($n, $i, 5);
}
var_dump($sequences);
Tell me if you need those hash sign surrounding the strings.
How would I go about generating this... I want to keep my primary key sequential and have a 12 digit unique pin generated for each new object added to the database.
The reason it cant just be autoincrement is i don't want the sequential numbers to be easily guessable.
It needs to be integer numbers, because I'm going to have verification codes that need to be dialed on a phone pad.
Use a concatenation of a unique incremented number and a randomly generated number.
The unique incremented number ensures that the result is unique, and the randomly generated number makes it hardly guessable.
This is simple and guaranteed to have no collision (1). The result is incremental, partly random, and non-predictable (provided that the random number part is generated with a good PRNG).
(1): You have to either pad id and random with zeros, or to separate them with some non-digit character.
With a MySQL db, this translates to:
CREATE TABLE foo (
id int not null auto_increment,
random int not null,
...
primary key (id)
);
Maybe you can use UUID_SHORT(). Not 12 digits long, but still could be a viable option:
mysql> select uuid_short();
+-------------------+
| uuid_short() |
+-------------------+
| 22048742962102272 |
+-------------------+
So:
INSERT INTO `table` (`id`, `text`) VALUES (UUID_SHORT(), 'hello world!');
Note: If you really want to have exactly 12 digits, then don't even try to substring the result, if would not ensure the uniqueness of the identifier and may cause collisions.
<?php
$allowed_characters = array(1,2,3,4,5,6,7,8,9,0);
for($i = 1;$i <= 12; $i++){
$pass .= $allowed_characters[rand(0, count($allowed_characters) - 1)];
}
echo $pass;
?>
demo: http://sandbox.phpcode.eu/g/c0190/4
Generally, I will prefer to do something a little bit more low tech. I obscure the values in PHP and leave them as auto-incrementing in JS.
$seeds = array( /*series 100 of very large >= 10-digit numbers*/ );
$seedID = rand( count( $seeds ) ); // randomly choose one of those.
// a string combination which represents the ID + some hash.
$id = bcadd( $seeds[ $seedID ], /* id retrieved from database */ );
// make sure we've not accidentally passed the 10^12 point
$id = bcmod( $id, 1000000000000 );
// make sure to pad
$id = str_pad('' . $id, 3, "0", STR_PAD_LEFT);
$outID = substr( $id, 0, 5 ) . $seedID . substr( $id, 6 );
Then, when receiving the ID from the user:
$seedID = substr( $outID, 6, 2 );
$tmpID = substr( $outID, 0, 5 ) . substr( $outID, 8 );
$id = bcsub( $tmpID, $seeds[ $seedID ] );
// we passed the modulus se we need to add this back in.
if( $id < 0 ) $id = bcmod( bcadd( $id, 1000000000000 ), 1000000000000 );
This will basically mean that you're simply obscuring whatever number you want -- you can use auto_increment with impunity!
One method would be to take your primary key value, salt it with a few other random-ish bits of data (username, current time, process ID, fixed string, etc...) and hash it with md5 or sha1. You then take the hash string and convert it into digits via basic string operations. That'll give you a relatively unique numeric code.
of course, with only 12 digits, you're far more likely to end up with a collision than by using the raw string hash - but since you're requiring this to be dialed on a keypad, it's an acceptable tradeoff.
If the pins are invalidated/deleted after usage, then the collision chances will be much reduced.
You want two things
Uniqueness
Incremental
If you want both the things from same sequence you will run out of luck (literally)
Uniqueness is guaranteed by having large sample space + random + check-unique. Which means, the actual number could be anywhere in between the sample space.
But if you want unique + incremental property, you are dividing sample space by 2. In 64 tries you would have reduced a 64 bit int sample space to 1 bit sample space.
Good luck !
All solutions so far lack one thing essential to your application: Security!
You said you will be using these numbers as a (product) verification code - so you really, really want this to be unpredictable, otherwise it will get exploited.
Neither MySQL's built-in RANDOM function nor any of the random functions PHP provides today are secure random functions. They behave pseudo-randomly, alright, but they all are predictable!
Your only chance is to whip up something of your own using /dev/urandom on a *nix machine or leveraging the Crypto API on Windows. OpenSSL does provide secure random numbers based on these mechanisms - you could reuse this either in a C extension for PHP or by reading the output from a command line script called from PHP. See also this answer.
About your requirement for the numbers to be sequential - is this really so important? It does complicate things enormously. Otherwise you would be good to go with a simple secure 6 byte random number encoded to a string using hex encoding (yielding a 12 character string). Although I would recommend making it 10 bytes and 20 characters to be safer.
But if you want to be sequential, which I interpret as monotonously increasing (because a simple +1 would be trivially predictable), this makes things just so much more complicated. And you don't gain anything from this complexity, the only thing that might happen is that you break the security by inventing some obscure scheme that is easily exploitable.
My suggestion: Add another column that acts as a plain old auto-incremented ID and add the code as a random number constructed as above as a separate column. As far as I see, there's no need to require the product activation code to be the ID at the same time.
I am developing a mysql database.
I "need" a unique id for each user but it must not auto increment! It is vital it is not auto increment.
So I was thinking of inserting a random number something like mt_rand(5000, 1000000) into my mysql table when a user signs up for my web site to be. This is where I am stuck?!
The id is a unique key on my mysql table specific to each user, as I can not 100% guarantee that inserting mt_rand(5000, 1000000) for the user id will not incoherently clash with another user's id.
Is there a way in which I can use mt_rand(5000, 1000000) and scan the mysql database, and if it returns true that it is unique, then insert it as the user's new ID, upon returning false (somebody already has that id) generate a new id until it becomes unique and then insert it into the mysql database.
I know this is possible I have seen it many times, I have tried with while loops and all sorts, so this place is my last resort.
Thanks
You're better off using this: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_uuid
Or using this: http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html
But if you actually want to do what you are saying, you can just do something like:
$x;
do {
$x = random_number();
"SELECT count(*) FROM table WHERE id = $x"
} while (count != 0);
// $x is now a value that's not in the db
You could use a guid. That's what I've seen done when you can't use an auto number.
http://php.net/manual/en/function.com-create-guid.php
Doesn't this function do what you want (without verification): http://www.php.net/manual/en/function.uniqid.php?
I think you need to approach the problem from a different direction, specifically why a sequence of incrementing numbers is not desired.
If it needs to be an 'opaque' identifier, you can do something like start with a simple incrementing number and then add something around it to make it look like it's not, such as three random numbers on the end. You could go further than that and put some generated letters in front (either random or based on some other algorithm, such as the day of the month they first registered, or which server they hit), then do a simple checksuming algorithm to make another letter for the end. Now someone can't easily guess an ID and you have a way of rejecting one sort of ID before it hits the database. You will need to store the additional data around the ID somewhere, too.
If it needs to be a number that is random and unique, then you need to check the database with the generated ID before you tell the new user. This is where you will run into problems of scale as too small a number space and you will get too many collisions before the check lucks upon an unallocated one. If that is likely, then you will need to divide your ID generation into two parts: the first part is going to be used to find all IDs with that prefix, then you can generate a new one that doesn't exist in the set you got from the DB.
Random string generation... letters, numbers, there are 218 340 105 584 896 combinations for 8 chars.
function randr($j = 8){
$string = "";
for($i=0;$i < $j;$i++){
srand((double)microtime()*1234567);
$x = mt_rand(0,2);
switch($x){
case 0:$string.= chr(mt_rand(97,122));break;
case 1:$string.= chr(mt_rand(65,90));break;
case 2:$string.= chr(mt_rand(48,57));break;
}
}
return $string;
}
Loop...
do{
$id = randr();
$sql = mysql_query("SELECT COUNT(0) FROM table WHERE id = '$id'");
$sql = mysql_fetch_array($sql);
$count = $sql[0];
}while($count != 0);
For starters I always prefer to do all the randomization in php.
function gencode(){
$tempid=mt_rand(5000, 1000000);
$check=mysql_fetch_assoc(mysql_query("SELECT FROM users WHERE id =$tempid",$link));
if($check)gencode();
$reg=mysql_query("INSERT INTO users id VALUES ('$tempid')",$link);
//of course u can check for if $reg then insert successfull