Send the next Row if we skip the previous Row in DB - php

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

Related

How to get next row of MySQL query in php without progressing when using mysqli_fetch_assoc()

I am trying to run a query to my mysql database through php and and am trying to get all the resulting rows. I also have to compare every row to the next row returned. I am trying to do this by setting the result variable to another temporary variable and calling mysqli_fetch_assoc() on that so that the while loop runs again for the next row. But what happens is that when I try to use mysqli_fetch_assoc() even on the other variables, somehow mysqli_fetch_assoc($result) also progresses to the next of the next row when while($row = mysqli_fetch_assoc($result)) goes to next iteration.
Here is the code example to illustrate this :
$query = "SELECT * FROM records ORDER BY num ASC;";
if($result = mysqli_query($conn, $query))
{
while($row = mysqli_fetch_assoc($result))
{
$temporaryresult = $result;
$rowtwo = mysqli_fetch_assoc($temporaryresult);// this makes mysqli_fetch_assoc($result) skip the next row which is unwanted
}
}
So how can I keep mysqli_fetch_assoc($result) from moving forward when I call mysqli_fetch_assoc($temporaryresult) ?
Any help would be appreciated.
am trying to do this by setting the result variable to another temporary variable and calling mysqli_fetch_assoc() on that so that the while loop runs again for the next row
It doesn’t work that way. Just because you assigned the resource id to a second variable, doesn’t mean that you now have a second result set that you could operate on separately. Both variables refer to the same resource id. Fetching a row will still move the row pointer of the “original” data set.
I also have to compare every row to the next row returned
Most likely, you are making things harder on yourself by trying to look ahead. Stuff like this is usually easier done when you look at the previous row instead. That one you have fetched already - so you don’t need to do an additional fetch now that would mess with the row pointer.
Pseudo code example:
$prevRow = null;
while($row = fetch(...)) {
if($prevRow) { // for the first row, this will still be null, so we only
// start comparing stuff when that is not the case
// compare whatever you need to compare here
}
...
$prevRow = $row;
}
After #CBroe's answer, I tried to solve this problem while still trying to look forward. I achieved this by storing the rows returned by the database and then looping through them. This makes it very easy too look ahead in the rows returned while avoiding the complexity of changing your code to look backwards.
$array = array();
// look through query
while($row = mysql_fetch_assoc($query)){
// add each row returned into an array
$array[] = $row;
}
Now, looping through these rows,
$i = 0;
for(;$i<count($array)-1;$i++)
{
if($array[$i]['somecolumn']==$array[$i+1]['anothercolumn'])//compare this column to another column in the next row
{
// do something
}
}
This successfully solved my problem. I hope it helps anyone stuck in the same position I was in.

Generating a random number per customer

I have this section of a form I need for my customer signup sheet I am creating.
My goal is check if the customer exists, and if they do, output the customerid into an input box. If the customer does not exist, a 6 digit number should be generated in the input box instead.
Here my code:
if(!empty($row['customerid'])) {
echo '<input class="input-xlarge focused" disabled id="focusedInput" name="customerID" value="'.$row['customerid'].'">';
} else {
$six_digit_random_number = mt_rand(100000, 999999);
$sql = "SELECT * FROM customers WHERE customerid='$six_digit_random_number'";
$loop = 0;
while($row = $result->fetch_assoc()){
if($loop == 10) {
echo "CANNOT GENERATE RANDOM NUMBER";
die();
}
$six_digit_random_number = mt_rand(100000, 999999);
$loop++;
}
echo '<input class="input-xlarge focused" disabled id="focusedInput" name="customerID" value="'.$six_digit_random_number.'">';
}
My question is, will the while loop actually stop duplicates from being made, or is there something I am overlooking?
--UPDATE
I switched out my original $six_digit_random_number with a number that already exists in the database, and the while loop did not change the number at all.
So I guess my updated question is, how can I check if the number has been used before?
Then you have to do something like this ( untested )
$sql = "SELECT customerid FROM customers WHERE customerid=:random_number";
$stmt = $DB->prepare($sql);
do{
//generate random number
$six_digit_random_number = mt_rand(100000, 999999);
//search for it
$stmt->excute([':random_number' => $six_digit_random_number]);
if( !$stmt->fetch() ){
//exit loop if it matches.
break;
}
}while(true);
//$six_digit_random_number save user with the unique number
Let me break the logic down for you.
When you create a new user, you need an infinite loop. On each iteration of that loop
Make a random number
Check against existing
Now when fetch returns false that means the number was never saved, so just use break to exit the loop. The last value of $six_digit_random_number is still set. So, after you exit the loop insert the new user with that number.
Next user cannot have the same number because, fetch will return a result, and the loop will continue and generate a new number. And so on, until such time as it creates one not in use.
The main drawback is if you have a lot of users you could make a bunch of queries against the DB, make sure to set that field to be an int and have a Unique index in the schema. That will improve the query performance, as will returning only one field ( that's all you need ) instead of *
Make sense.
-note- I like to use the do{ }while(true); syntax for this as it's less likely to be mistaken as a typo latter. It's just more readable then using while(true){ } other then that the only difference is with a do loop the loop happens before the evaluation so do{ }while(0) will run 1 time where while(0){} will not. In this case that doesn't matter though.

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

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

Is this a PHP or MySQL bug?

$allUsersResult = mysql_query("SELECT * FROM users");
// the purpose of this line was to grab the first row for use
// separately in a different area than the while loop
$user = mysql_fetch_assoc($allUsersResult);
while($users = mysql_fetch_assoc($allUsersResult)){
// the first row is not available here
}
So is this a bug or is it my fault for doing it wrong?
PS: this is only for example. I'm not using both $user and the while loop right next to each other like this, they are used in different places in the script.
You need to drop
$allUsers = mysql_fetch_assoc($allUsersResult);
It's taking your first result row.
Answer to new question: No. It is not a design flaw in PHP. It's a flaw in your program design. You need to rethink what you are doing.
Why do you need the first value separated out? Are you relying on it to be a specific row from your table all of the time? If you alter your table schema it's very possible that the results will be returned to you using some other sorted order.
Perhaps if you tell us what you are trying to do we can give you some design suggestions.
It is your fault. By calling $allUsers = mysql_fetch_assoc($allUsersResult); first, you already fetch the first row from the result set. So just remove that line, and it should work as expected.
edit: Per request in comment.
$user = mysql_fetch_assoc($allUsersResult);
if ( $user ) // check if we actually have a result
{
// do something special with first $user
do
{
// do the general stuff with user
}
while( $user = mysql_fetch_assoc($allUsersResult) );
}
Which is considered as bad code by some IDEs (two statements in one row). Better:
$allUsersResult = mysql_query("SELECT * FROM users");
$user = mysql_fetch_assoc($allUsersResult);
while($user){
// do stuff
doStuff($user)
// at last: get next result
$user = mysql_fetch_assoc($allUsersResult)
}
When you use mysql_fetch_assoc(), you are basically retrieving the row and then advancing the internal result pointer +1.
To better explain, here is your code:
$allUsersResult = mysql_query("SELECT * FROM users");
//Result is into $allUsersResult... Pointer at 0
$user = mysql_fetch_assoc($allUsersResult);
// $user now holds first row (0), advancing pointer to 1
// Here, it will fetch second row as pointer is at 1...
while($users = mysql_fetch_assoc($allUsersResult)){
// the first row is not available here
}
If you want to fetch the first row again, you do not need to run the query again, simply reset the pointer back to 0 once you have read the first row...
$allUsersResult = mysql_query("SELECT * FROM users");
//Result is into $allUsersResult... Pointer at 0
$user = mysql_fetch_assoc($allUsersResult);
// $user now holds first row (0), advancing pointer to 1
// Resetting pointer to 0
mysql_data_seek($allUsersResult, 0);
// Here, it will fetch all rows starting with the first one
while($users = mysql_fetch_assoc($allUsersResult)){
// And the first row IS available
}
PHP Documentation: mysql_data_seek()
I'll go ahead and answer my own question on this one:
$allUsersResult = mysql_query("SELECT * FROM users");
// transfer all rows to your own array immediately
while($user = mysql_fetch_assoc($allUsersResult)){
$allUsers[] = $user;
}
// use first row however you like anywhere in your code
$firstRow = $allUsers[0];
// use all rows however you like anywhere in your code
foreach($allUsers as $user){
// do whatever with each row ($user). Hey look they're all here! :)
}

Categories