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) ?
Related
I have a php script that runs every 20 minutes (I use screen on Debian - server side),
The php script selects the first row of the Database using LIMIT 1, like:
$q = mysqli_query($connection,"SELECT * FROM table_name LIMIT 1");
Once selected the first Row it will send the result to a telegram bot, delete the 1st row from the DB and the scripts ends.
It repeats this process every 20 minutes cause the screen.
Now, the question is, if the first row I got with LIMIT 1 doesn't meet the criteria (at the moment it is deleted and skip the actual process until the next 20 minutes), how can I make it to select the 2nd row (which is now the 1st row again) to get the new data using the same script and avoiding wait the next 20 minutes?
Is it possible with my actual script LIMIT 1?
Obviously I don't have access to your database, and I don't know what your criteria are, so this will all be pseudocode. I am assuming you will want to delete the first row if it doesn't match the criteria, but it would be just as possible to only skip it:
$finished = false;
while(!$finished) {
// you already have the code to get the first row. It goes here.
// I will assume those results are in $row
$finished = $row MEETS CRITERIA; // whatever these criteria are
// you also have the code to delete the current row. put that here
// whether this row satisfies the condition or not. The row you
// just selected will always be deleted
}
It's as simple as that. If the row met the criteria, then $finished is TRUE and the while loop will terminate*. If the row didn't meet the criteria, the $finished is FALSE and the while loop will run again. It will run until a row meets the criteria and $finished is TRUE.
* ! is the php NOT operator. It inverts the value. So basically what the while loop is saying is this:
while not finished { do this code }
Also, to avoid a non-terminating loop, you'll need to make sure that your result set has something in it, or else $finished will never be set to TRUE. I'm not sure how you're executing, so I'm not going to suggest an implementation. Just be aware that it is possible for a while to become a neverending loop and take measures to avoid it. For instance, you might:
$finished = false;
$retries = 5;
while(!$finished && $retries-->0) {
//the code
}
This decrements $retries every loop, and once it gets to 0 the while will stop no matter what. It's a good idea to put a failsafe in any loop that might accidentally (or unforeseenedly) become unterminated, especially during development, so you don't hang your code.
EDITED now that I have seen some of the sample code:
$finished = false;
$retries = 5;
while(!$finished && $retries-->0) {
$q = mysqli_query($connection, "SELECT * FROM table LIMIT 1");
$r = mysqli_fetch_assoc($q);
$e = $r['id'];
$asin_queue = $r['asin'];
$price_queue = $r['new_price'];
$r = file_get_contents("https://api.keepa.com/product?asin=$asin_queue&key=$api_keepa");
$gz = gzdecode($r);
$t = json_decode($gz);
$new_price = $t->products[0]->csv;
// put any other stuff you need to do here,
// like updating any db entries, etc
// you also have the code to delete the current row. put that here
// whether this row satisfies the condition or not. The row you
// just selected will always be deleted
$finished = $new_price <= $price_queue;
}
And that's pretty much it. It's your existing code, just wrapped in a while block so that it loops. The only thing I've added is a line to check for a condition inside the while block so that the code knows when to exit.
Also, let's look at this: $retries-->0, because that line may be a little confusing. So I said you needed some kind of failsafe to keep your code from accidentally looping forever if you make a mistake or oversight, and so I assign a variable $retries and make it equal to 5. -- is php shorthand for "subtract one from this", so $retries-- just subtracts one from whatever the value is currently*. >0 is just a value check, even though I wrote it so it all runs together, it's two separate things. It could have been written like this: $retries-- > 0.
In context of the while loop, there's a few things happening:
while( !$finished && $retries-->0 ) {}
and the whole thing has to evaluate to TRUE for the loop to keep running. && is the logical operator AND. ! is the logical operator NOT. So in plain language terms that condition would read:
"while not finished and the number of remaining retries is greater than 0, continue looping."
Hope all this helps.
* -- can go before or after the variable it's modifying. If it goes after ($retries--) then the decrement doesn't take effect immediately. This lets you use the variable in a logical test before the decrement is applied. 5-- > 4 is true. If you put the -- before the variable, the decrement happens first. --5 > 4 would be false, because the subtraction will happen before the comparison.
Thanks for replying back!
I think I can use your suggestion but I need to edit it, the check I need to perform is as follows:
$q = mysqli_query($connection,"SELECT * FROM table LIMIT 1");
$r = mysqli_fetch_assoc($q);
$e = $r['id'];
$asin_queue = $r['asin'];
$price_queue = $r['new_price'];
//perform the check to see if the price has changed
$r = file_get_contents("https://api.keepa.com/product?asin=$asin_queue&key=$api_keepa");
$gz = gzdecode($r);
$t = json_decode($gz);
//price updated
$new_price = $t->products[0]->csv;
//here is where I am stuck
I should do something like this:
while($new_price > $price_queue){
*// It will need to delete the first row on the DB and do the check again, until it get the right condition $new_price <= $price_queue and then it can quit the while loop.
//repeat the above to do the check again*
$q = mysqli_query($connection,"SELECT * FROM table LIMIT 1");
$r = mysqli_fetch_assoc($q);
$e = $r['id'];
$asin_queue = $r['asin'];
$price_queue = $r['new_price'];
//perform the check to see if the price has changed
$r = file_get_contents("https://api.keepa.com/product?asin=$asin_queue&key=$api_keepa");
$gz = gzdecode($r);
$t = json_decode($gz);
//price updated
$new_price = $t->products[0]->csv;
}
So I was trying to make a "create new user" function in PHP which should basically check in the database which is the lowest possible ID to assign to that new user. But I have now tried around with so many different methods and they all do not work as they should. Here is my current version:
function newUser($connection) {
$notNewId = sqlsrv_query ($connection, "SELECT id FROM users"); //get id from users table
while($notNewId2 = sqlsrv_fetch_array ($notNewId)) {
for ($i = 0; $i <= sizeOf($notNewId2); $i++) {
foreach ($notNewId2 as $key => $value ) {
if ($i != $value) {
break;
}
}
}
$id = $i;
return $id;
}
}
the $connection is a element of type sqlsrv_connect.
as far as I can tell my current version should be able to read the ids and put them in an array, but from there on something went wrong. As well... I may have to sort the array after id, but I have no clue how to do that.
I would really appreciate any help, even if it's no actual code and just the logic explained, thx.
EDIT: Seems like it is not clear enough, what I want. My script should assign a new ID to the new row in the users table, if that is possible automatically somehow with SQL, then please explain to me how. (Right now ID is not a primary key, I will change that as soon as I can)
Taking the highest number and adding one is not enough (like when I have 0, 1, 2, 4 and 5, the new ID should be 3, not 6). But still thanks, I didn't knew about that MAX thing.
Assuming id is your primary key and you're not trying to auto increment it in your users table, you can find the maximum value of it and add 1 in your SQL query. This mitigates the need for your nested for loops. Use ISNULL to check if that value is not null and default to 1 otherwise.
function newUser($connection) {
$query = sqlsrv_query ($connection, "SELECT ISNULL(MAX(id)+1, 1) FROM users");
return sqlsrv_fetch($query);
}
$project_query= $db->query("SELECT * FROM projects WHERE id='$task_info->project_id'");
$project_info = $project_query->fetch_object();
$admins_array = $project_info->admins_array;
$admins = unserialize($admins_array);
$is_admin = false;
foreach($admins as $value) {
if($value == $_SESSION['username']) { $is_admin = true; }
}
I have the following code that checks if the currently logged user's username is contained in a serialized array that is stored in a mysql table row. How would I go about optimizing it for maximum performance?
1) if changing your db structure is a possibility, I suggest storing the serialized values in a separate table
project_admins (project_id, user_id) with a unique index on (project_id,user_id)
Then you can quickly determine whether a user is an admin of a project
SELECT 1 from project_admins where project_id = x and user_id = y
2) if you're stuck with storing serialized data in the db I suggest storing the serialized array indexed by username
so you can search it in constant time
i.e.
$is_admin = array_key_exists($_SESSION['username'],$admins);
3) if you can not index the admins array by username, you can minimally optimize your loop by adding a break statement once you find a match
foreach($admins as $value) {
if($value == $_SESSION['username']) {
$is_admin = true;
break; // match found no need to check remaining values
}
}
Actually in this case you're probably better off using in_array
$is_admin = in_array($_SESSION['username'],$admins);
If it's optimization in terms of speed, then I would suggest performing the login in SQL.
It would require you to change your table structure to a more normalised state by switching from serialised strings into separate related entities.
I need to generate a random number in the format 105-##### and I need to check this generated number against the already created numbers before saving it to the database to make sure it doesn't exist.
Here is my code to generate the number itself:
$digits = 5;
$second_num = str_pad(rand(0, pow(10, $digits)-1), $digits, '0', STR_PAD_LEFT);
$order_gen = "105-".$second_num;
Now that I have this $order_gen number, what is the best way to loop through what I already have in the order_number database table to make sure there are no duplicates? I was thinking of using a while loop but not sure how to apply it.
Given your algorithm, there isn't a better way except to query the database for each ID you generate, until you find one that isn't taken.
Wrap the entire thing in a do/while statement, and define a method which queries the database by that random ID, returning true if the record exists, and false otherwise:
do {
$digits = 5;
$second_num = str_pad(rand(0, pow(10, $digits)-1), $digits, '0', STR_PAD_LEFT);
$order_gen = "105-".$second_num;
} while (record_exists($order_gen);
Alternatively, generate randomly once, and then increment until you find a number that isn't taken:
$digits = 5;
$second_num = str_pad(rand(0, pow(10, $digits)-1), $digits, '0', STR_PAD_LEFT);
$order_gen = "105-".$second_num;
while (record_exists($order_gen) {
$second_num += 1
$second_num = str_pad($second_num, '0', STR_PAD_LEFT);
$order_gen = "105-".$second_num;
}
There are 3 options that I can think of:
Generate an id and query to see if it is used. If used, generate another and repeat until you find one that is not used. This will probably be the slowest.
Have a table with all possible ids. Select a random row from that table, use that id and delete it so it won't be used again. This requires a table just for holding these ids though. Depending on your needs, this might not be possible.
In PHP, query all existing ids order by id. Loop over result and make an array of all ids. From there you can generate, if exists in array generate again like #1 or make an array of all possible ids, array_diff($allIDS, $usedIDs) to find ones not in use and array_rand to get a random one. This option uses more memory in php having to query out all existing ids.
All 3 methods will possibly suffer from race conditions where an id could be duplicated if two requests happened at the same time. #2 would probably be easiest to prevent race conditions. On the delete query, check the count of affected rows (PDOStatement::rowCount if using PDO) and if 0, assume someone else got it before you could use it and get another id.
This can do the work
$rand = mt_rand(000123,999999); //prefix between 000123 to 999999
$id = "105".$rand;
$error = true;
while($error){
$query = "SELECT id FROM table WHERE id = '$id'";
$result = mysqli_query($conn,$query);
$count = mysqli_num_rows($result);
if($count!=0){
$error = true;
$rand = mt_rand(000123,999999);
$id = "105".$rand;
}else{
$error = false;
}
}
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