I'm new to php so I'm having trouble seeing where I'm going wrong with this code. I'm trying to pull a set of 4 random IDs to display their images and links on the page. Running the same code on my local dev environment works fine and the response is quick. On my VPS, however, the page will take 15-30 seconds to load. I managed to isolate the issue to this loop by just taking the whole block out and the page loads quickly again.
<?php
$alreadypicked = array();
for ($i = 1; $i <= 4; $i + 0) {
$randchoice = getinfoForRandom();
$info = pullrandinfo($randchoice);
if (!in_array($randchoice, $alreadypicked)) {?>
<div class="col-sm-3 col-xs-6">
<a href="<?php echo 'recipes.php?id=' . $info[0]['id'];?>">
<img class="img-responsive portfolio-item" src="<?php echo 'images/' . $info[0]['filename'];?>" alt="<?php echo $info[0]['name'];?>">
</a>
</div>
<?php
array_push($alreadypicked, $randchoice);
} else {
$i = $i - 1;
}
}?>
I tried doing the same with a while loop instead of an if/else statement, but that did not fix the slow loading.
Other relevant parts of code:
// Gets all IDs in database
function getIDs() {
$handler = dbconnector();
$query = $handler->query('SELECT id FROM dishes');
$query->setFetchMode(PDO::FETCH_ASSOC);
$output = $query->fetchAll();
$array = array();
for ($i = 0; $i < count($output); $i++) {
array_push($array, $output[$i]['id']);
}
return $array;
}
// Chooses a random ID that exists
function getinfoForRandom() {
$IDs = getIDs();
$totalIDs = count($IDs);
do {
$random = mt_rand(1, max($IDs));
} while (!in_array($random, $IDs));
return $random;
}
// Pulls data for the randomly selected ID
function pullrandinfo($id) {
$handler = dbconnector();
$query = $handler->query('SELECT id, name, filename FROM dishes WHERE id = ' . $id);
$query->setFetchMode(PDO::FETCH_ASSOC); // Fetches only assoc array from the query above
$data = $query->fetchAll();
return $data;
}
You can improve performance by limiting the number of queries that you execute.
Your code appears to execute two queries in each loop, one in getIds() and the other in pullrandinfo(). These queries are expensive and time-consuming.
Instead, fetch your IDs in a single query making use of SQL's RAND().
SELECT id FROM <table> ORDER BY RAND() LIMIT 4;
Then fetch all of the IDs in a single query:
'SELECT ... WHERE id IN (' . implode(',', $ids) . ')...'
Finally, use your loop only to output the data.
Related
I need to create a 'wall' of images.
There's my code:
<?php
$q = $db->prepare("SELECT * FROM wall");
$q->execute();
while($row = $q->fetch(PDO::FETCH_ASSOC)) { ?>
<div class="col-4-perc"><img src="<?=$site?>/assets/img/wall/<?=$row['source']?>.png"
alt="Foto wall" class="img-fluid"></div>
<?php } ?>
I would like to repeat the while loop repeating the photos in the database at least 3/4 times but I don't know how to do it.
As explained in the comments, you have to use FetchAll instead of fetch in a while. Here is a example script for the result you want:
$q = $db->prepare("SELECT * FROM wall");
$q->execute();
$allImage = $q->fetchAll() ;
$limit = 100 ;
$activeKey = 0 ;
for ($i = 0; $i < $limit ;$i++){
if (!isset($allImage[$activeKey]['source'])) {
$activeKey = 0 ;
// if the line does not exist, reset to the first key (0)
}
echo "<div class='col-4-perc'><img src='{$site}/assets/img/wall/{$allImage[$activeKey]['source']}.png'
alt='Foto wall' class='img-fluid'></div>";
$activeKey++ ;
}
Images are saved in $allImage, a table with numeric indexes. Next we just have to loop with a simple for using $limit (the max image you want display).
And $activeKey for the curent displayed image, if $allImage[$activeKey] is not set, reset to 0.
I have this script executing as a cron job everyday to update days remaining to pay invoices. I first query every row of my table and attempt to store the data in a multidimensional array but this seems to be storing everything I query in the first element of my array.
Here's my script:
<?php
include '../inc/dbinfo.inc';
ini_set("log_errors", 1);
ini_set("error_log", "/tmp/php-error.log");
error_log( "################################################# UpdateVendorInvoiceDays.php #################################################" );
$three = 3;
$fetchAllInvoices = "SELECT VENDORINVOICEID, VdrInvoiceReceived, PaymentDue, COUNT(*), DATEDIFF(PaymentDue, NOW()) FROM tblVendorInvoices WHERE VdrInvoiceStatusID != ?";
$getInvoices = $conn->prepare($fetchAllInvoices);
$getInvoices->bind_param("i", $three);
$getInvoices->execute();
$result = $getInvoices->get_result();
$rows = array();
$j = 0;
while($row = $result->fetch_assoc())
{
$rows[$j][] = $row;
$j++;
}
echo json_encode($rows[0][0]); //Only outputs one row
//UPDATE DAYS REMAINING IN EACH ENTRY THAT ISNT PAID
$updateDaysRemaining = "UPDATE tblVendorInvoices SET DaysRemaining = ? WHERE VENDORINVOICEID = ? AND VdrInvoiceStatusID ! = ?";
$setDays = $conn->prepare($updateDaysRemaining);
$k = 0; //incrementor
$numberOfEntries = $rows['COUNT(*)'];
for($k;$k<$numberOfEntries;$k++){
$setDays->bind_param("iii", $rows[$k]["DATEDIFF(PaymentDue, NOW())"],
$rows[$k]['VENDORINVOICEID'], $three);
if($setDays->execute()){
error_log('Cron success');
}else{
error_log('Cron fail');
}
}
?>
Currently the output from my first query is:
[[{"VENDORINVOICEID":88,"VdrInvoiceReceived":"2018-08-21","PaymentDue":"2018-07-27","COUNT(*)":2,"DATEDIFF(PaymentDue, NOW())":-25}]]
and my error log only gives me a notice for $rows['COUNT(*)'] being undefined (which makes sense)
I've looked at other answers here but they don't seem to have the same structure as I do.
EDIT: I also have 2 rows in my database but this only puts out one. I forgot to mention this.
There are a couple of simplifications to get all of the rows. Instead of...
while($row = $result->fetch_assoc())
{
$rows[$j][] = $row;
$j++;
}
echo json_encode($rows[0][0]);
You can just return all rows using fetch_all()...
$rows = $result->fetch_all (MYSQLI_ASSOC);
echo json_encode($rows);
Then encode the whole array and not just the one element - which is what $rows[0][0] was showing you.
As for you other problem - change in your select statement to
COUNT(*) as rowCount
and then you can use this alias for the field reference...
$rows['COUNT(*)']
becomes
$rows['rowCount']
I didn't know exactly how to word this question but by do something I mean that I would like to hide or not show my "next" button that is shown below. I have a script that pulls all the images from MySQL and prints them to my page by 30 images per page and the next 30 will create a new page that is activated by my back/next buttons. My "back" button has a if statement if $startrow isn't >= 0 than it won't show but I would like the same concept with my next button when the last row in my database is shown and it hides my next button.
I was thinking if you can detect the first empty row or the last row of the database and if so hide the next button. Otherwise it keeps adding 30 to $startrow when nothing is shown on screen.
I found a script helping me with this here but it didn't tell me how to hide the next button.
<?php
$startrow = $_GET['startrow'];
if (!isset($_GET['startrow']) or !is_numeric($_GET['startrow'])) {
$startrow = 0;
} else {
$startrow = (int)$_GET['startrow'];
}
?>
<?php
$db = mysqli_connect("localhost", "root", "", "media");
$uploaded = mysqli_query($db, "SELECT * FROM images LIMIT $startrow, 30");
while ($row = mysqli_fetch_array($uploaded)) {
echo "<div class='img_container'>";
echo "<li><img class='img_box' src='uploads/images/".$row['image_title']."' ></li>";
echo "</div>";
}
$prev = $startrow - 30;
if ($prev >= 0) {
echo '<div class="prevRow">Back</div>';
}
echo '<div class="nextRow">Next</div>';
?>
You could try something like
$num_rows = 30; // rows on a page
$db = mysqli_connect("localhost", "root", "", "media");
// get total possible rows
$res = mysqli_query($db, "SELECT count(id) FROM images");
$row = $res->fetch_row();
$total_rows = $row[0];
$res->close();
$uploaded = mysqli_query($db, "SELECT * FROM images LIMIT $startrow, $num_rows");
while ($row = mysqli_fetch_array($uploaded)) {
. . .
}
$prev = $startrow - $num_rows;
if ($prev >= 0) {
echo '<div class="prevRow">Back</div>';
}
if ( $startrow+$num_rows < $total_rows ) {
echo '<div class="nextRow">Next</div>';
}
Potentially a little faster than the answer from #RiggsFolly, you can modify your existing query to count the rows.
SELECT SQL_CALC_FOUND ROWS * FROM images LIMIT $startrow, 30
Then, after the query returns, you run a second query to get the answer:
SELECT FOUND_ROWS()
The FOUND_ROWS() function returns the number of rows the previous query would have returned, without LIMIT (or an offset).
This is probably not as fast as your original query would be in isolation, but should be slightly faster than SELECT COUNT(...) ... followed by your original query. With small data sets, though, any differences will likely be below measurable limits.
See also https://dev.mysql.com/doc/refman/5.7/en/information-functions.html
You can also combine these things into a stored procedure that accepts items per page and page number, and returns all of the records along with metadata items such as the total number of pages.
Need a little help, advise, or link to an example or useful tutorial so I can learn this. As I am very new to programming in general.
I have 11 Select boxes in a form named 'ore1' thru 'ore11' that send data up to $_POST.
This little block turns that into an array that is used in another function.
//function calculateValue($sellValue){ -- add when you figure it out
if(isset($_POST['submit'])){
$i = 11 //total number of select boxes
$pdquery = "SELECT * FROM ore
WHERE '".$_POST['ore1']."'
LIKE id";
$result = mysql_query($pdquery);
$oredata1 = array();
while ($row = mysql_fetch_row($result)) {
$oredata1 = $row; }
}
else {}
}
I'd like like to be able to use this one bit of code with all 11 Select boxes without having to copy it 11 times by getting
.$_POST['ore#']. //5th line
$oredata# = $row; //10th line
to replace # with the appropriate # depending on the select box it is used on.
Any tips?? Thanks in advance.
In your HTML:
<select name="ore[]">
...
</select>
<select name="ore[]">
...
</select>
In your PHP
if(isset($_POST['submit']) && isset($_POST['ore'])){
foreach($_POST['ore'] as $ore){
$ore = (int)$ore;
$pdquery = "SELECT * FROM ore WHERE id = $ore";
...
}
}
Also do not use mysql_* function since deprecation process has begun
for ($i = 1; $i <= 11; $i++) {
$ore = 'ore'.$i;
$pdquery = "SELECT * FROM ore WHERE '".$_POST[$ore]."' like id";
...
execute it in a for-loop - from 1 to 11. Then you can use the variable $i to access stuff.
// I assume that there is some MySQLi object called $mysqli already present. You'll have to create it if it does not
if(isset($_POST['submit'])){
$pdquery = 'SELECT * FROM ore WHERE id LIKE ?';
$oredata = array();
if($stmt = $mysqli->prepare($pdquery)) {
for($i = 1; $i < 12; $i++) {
$ore = 'ore' . $i;
if(empty($_POST[$ore])) {
continue;
}
$oreValue = (int)$_POST[$ore];
$stmt->bind_Param('i', $oreValue);
$stmt->execute();
$oredata[$i] = $stmt->get_result()->fetch_array(MYSQLI_ASSOC);
}
}
$stmt->close();
}
This does NOT take into consideration the fact that there might be more than just those eleven inputs. Using an array on HTML side would certainly be cleaner (And you'd just have to replace the for-loop with a foreach-loop and use the key instead of $ore)
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.