I am having trouble with changing over some code from old style mysql queries to being prepared. I assume the problem is due to the fact that I'm using multiple whiles which each have their own query in which is causing problems cause only one prepared statement can be active at a time.
EDIT: If anyone cares, I've made it work with only 2 loops like so -
function createDeskMenu()
{
global $bookingTimes, $dbconn;
$day0 = mktime(0, 0, 0, date("m") , date("d"), date("Y"));
$query = "SELECT location FROM location";
$result = mysqli_query($dbconn,$query);
mysqli_num_rows($result);
while ($row = mysqli_fetch_array($result))
{
$location = $row['location'];
echo "<h3>$location</h3><div>";
$query = $dbconn -> prepare("SELECT COALESCE( CountDesk, 0 ) total, name, d.desk_id, phone, fax, dock, pc FROM desk d LEFT JOIN (SELECT COUNT(booked.desk_id) CountDesk, desk_id FROM booked WHERE booking_id >=?)b ON d.desk_id = b.desk_id WHERE location=?");
$query->bind_param("is",$day0, $location);
$query->execute();
$query->bind_result($totalCount,$name,$desk_id,$phone,$fax,$dock,$pc);
while($query->fetch()) {
$total = count($bookingTimes) * 14 - $totalCount;
echo '<a href="?page=desk&desk='.$desk_id.'"><div class="desk"><b>'.$name.'
('.$total.' Available Bookings)</b><li>Facilities:';
if($phone){echo " Phone,";}if($fax){echo " Fax Machine,";}if($dock){echo " Laptop Dock.";}if($pc){echo " Desktop Workstation.";}
echo '</li></div></a><hr />';
}
$query->close();
echo '</div>';
}
}
You cannot prepare() a statement while the connection has rows waiting to be fetched from another statement. You must first either close the previous result set or fetch all rows from it.
However...
I don't see the need for the outer query which retrieves location at all, as it has no WHERE clause. You are selecting all locations, and can therefore omit that part entirely. All you are using the outer loop for is to create a <h3> for each location, and this is extremely wasteful ( in addition to originally causing you breakage in the code)
Instead, do one query and in the fetch loop, check if the location has changed. When it changes, output your header
echo "<h3>$location</h3><div>";
So remove the outer query and loop entirely, and use a pattern like the following to detect changes in location. Make sure to ORDER BY location so they are sorted for you.
No bound parameters are needed. You can do this with a query() call since the location is no longer variable and $day0 is known to be a timestamp from mktime().
// Substitute a query() call and $day0 can be inserted directly.
// This one query fetches all locations sorted...
$query = $dbconn->query("
SELECT
COALESCE( CountDesk, 0 ) total,
name,
d.desk_id,
phone,
fax,
dock,
pc
FROM
desk d
LEFT JOIN (
SELECT COUNT(booked.desk_id) CountDesk, desk_id FROM booked WHERE booking_id >= $day0
)b ON d.desk_id = b.desk_id
ORDER BY location");
// Store the last location in a variable which starts empty...
$location = "";
while($row = $query->fetch_assoc()) {
// on change of $location, update the variable.
if ($location !== $row['location']) {
$location = $row['location'];
// And output the new location value
echo "<h3>$location</h3><div>";
}
// Do the rest of your loop.
$total = count($bookingTimes) * 14 - $row['total'];
echo '<a href="?page=desk&desk='.$row['desk_id'].'"><div class="desk"><b>'.$row['name'].'
('.$total.' Available Bookings)</b><li>Facilities:';
if($row['phone']){
echo " Phone,";
}
if($row['fax']){
echo " Fax Machine,";
}
if($row['dock']){
echo " Laptop Dock.";
}
if($row['pc']){
echo " Desktop Workstation.";
}
echo '</li></div></a><hr />';
}
$row->close();
echo '</div>';
Now on to the reason it was failing.... You cannot prepare() a new statement while there are rows remaining to be fetched from a previous statement or query. You must first either fetch all the rows, or close the statement with $stmt->close(). So effectively you cannot nest fetch loops.
The better method is to first fetch all rows into an array and then loop over that array:
while ($row = $first_query->fetch()) {
// Append all onto an array
$first_query_rows[] = $row;
}
// Then loop over that
foreach ($first_query_rows as $row) {
// Do a new query with $row
}
Usually though, this can be solved with a proper JOIN.
Related
Can't believe this is giving me a problem but here it is. An example of the query and code is:
$sql = "SELECT
o.order_id, o.order_number, o.broker_id, o.agent_id
FROM orders o";
$sql_res = safe_query($sql);
/* I want to echo broker_id and angent_id only once here,
before the while loop, because the values are all the same */
while ($row = pg_fetch_array($sql_res)){
echo $order_number;
}
Assume broker and agent id numbers are each the same in every row. I don't want to echo them every time in the loop. I just want them to echo one time before the loop. Since they are the same, it does not matter which row they are echoed from.
I figured out a different way for a means to an end. It works well.
$sql = "SELECT
o.order_id, o.order_number, o.broker_id, o.agent_id
FROM orders o";
$sql_res = safe_query($sql);
$count = 0;
while ($row = pg_fetch_array($sql_res)){
if ($count == 0) {
echo $broker_id;
echo $agent_id;
}
echo $order_number;
$count += 1;
}
I realize I'm echoing the broker and agent IDs inside the while loop, but it's only an echo the first time through, and I can display them at top. And then every unique order is echoed. Visually, it accomplished what was needed for the end user.
Alright,
I've got a multiple select dropdown on a page called week-select, its selections get passed via ajax to my php page.
I can get the data just fine, but when the query runs it doesn't complete appropriately.
I've got this:
//Deal with Week Array
$weekFilter = $_GET['week']; /*This is fine, if it's 1 week the query works great (weeks are numbered 12-15), but if it is 2 weeks the result is formatted like this 12-13 or 13-14-15 or whichever weeks are selected*/
$weekFilter = str_replace("-",",",$weekFilter); /*This works to make it a comma separated list*/
.../*I deal with other variables here, they work fine*/
if ($weekFilter) {
$sql[] = " WK IN ( ? ) ";
$sqlarr[] = $weekFilter;
}
$query = "SELECT * FROM $tableName";
if (!empty($sql)) {
$query .= ' WHERE ' . implode(' AND ', $sql);
}
$stmt = $DBH->prepare($query);
$stmt->execute($sqlarr);
$finalarray = array();
$count = $stmt->rowCount();
$finalarray['count'] = $count;
if ($count > 0) { //Check to make sure there are results
while ($result = $stmt->fetchAll()) { //If there are results - go through each one and add it to the json
$finalarray['rowdata'] = $result;
} //end While
}else if ($count == 0) { //if there are no results - set the json object to null
$emptyResult = array();
$emptyResult = "null";
$finalarray['rowdata'] = $emptyResult;
} //end if no results
If I just select one week it works great and displays the appropriate data.
If I select multiple options (say weeks 12, 14 and 15) it runs the query but only displays week 12.
When I manually input the query in SQL, how I imagine this query is getting entered - it runs and displays the appropriate data. So if I put SELECT * FROM mytablename WHERE WK IN ( 12, 14, 15 ) it gets exactly what I want.
I can't figure out why my query isn't executing properly here.
Any ideas?
**EDIT: I make the array from the multiple selections a string using javascript on the front end before it is passed to the backend.
Your resulting query with values probably looks like this with a single value in IN:
… WK IN ("12,14,15") …
Either use one placeholder for each atomic value:
if ($weekFilter) {
$values = explode(",", $weekFilter);
$sql[] = " WK IN ( " . implode(",", array_fill(0, count($values), "?")) . " ) ";
$sqlarr = array_merge($sqlarr, $values);
}
Or use FIND_IN_SET instead of IN:
$sql[] = " FIND_IN_SET(WK, ?) ";
I don't think you can bind an array to a singular ? placeholder. Usually you have to put in as many ? values as there are elements in your array.
If your HTML is correct and your week select has name="week[]", then you will get an array back with $_GET['week'];, otherwise without the [] it will only give you 1 value. Then, you're doing a string replace, but it's not a string. Instead, try this:
$weekFilter = implode(',', $_GET['week']);
I'm trying to write a script that gets data from a sql server based on the id of the entry in my data base. when I try to access the page using the link with the id of the entry it returns as if it does not recognize the id. Below is the php code :
<?php
require('includes/config.inc.php');
require_once(MYSQL);
$aid = FALSE;
if (isset($_GET['aid']) && filter_var($_GET['aid'], FILTER_VALIDATE_INT, array('min_range' => 1)) ) {
$aid = $_GET['aid'];
$q = "SELECT aircraft_id, aircraft_name AS name, aircraft_type AS type, tail_number AS tn FROM aircraft USING(aircraft_id) WHERE aircraft_id = $aid";
$r = mysqli_query($dbc, $q);
if (!(mysqli_num_rows($r) > 0)) {
$aid = FALSE;
}
}// end isset tid
if ($aid) {
while ($acdata = mysqli_fetch_array($r, MYSQLI_ASSOC)){
echo'<h2>'. $acdata['tail_number'] .'</h2>';
}
} else {
echo '<p>This pages was accessed in error.</p>';
}
?>
Any hints?
Try to var_dump($q); before $r = mysqli_query($dbc, $q); to inspect your query and then just execute it through phphmyadmin or in MySQL server terminal directly and see what does it return.
Update:
Use var_dump($q);die(); to stop script from executing after dumping.
You are using a field alias in your query, so you must use that in your echo to:
echo'<h2>'. $acdata['tn'] .'</h2>';
Get rid of the USING(aircraft_id), it causes an error and your query doesn't execute.
"SELECT aircraft_id, aircraft_name AS name, aircraft_type AS type, tail_number AS tn FROM aircraft WHERE aircraft_id = $aid"
I guess it's a leftover from a previous version of the query? Using (id) is a shortcut for
FROM
foo
INNER JOIN bar ON foo.id = bar.id
It can be used when the tables to be joined are joined on columns which have the same name. Just shorter to write.
Since you are not joining you have to remove it.
I am creating a pagination script and I need to get the first and last results in the database query so that I can determine what results appear when the user clicks a page to go to. This is the code that I have at the minute:
// my database connection is opened
// this gets all of the entries in the database
$q = mysql_query("SELECT * FROM my_table ORDER BY id ASC");
$count = mysql_num_rows($q);
// this is how many results I want to display
$max = 2;
// this determines how many pages there will be
$pages = round($count/$max,0);
// this is where I think my script goes wrong
// I want to get the last result of the first page
// or the first result of the previous page
// so the query can start where the last query left off
// I've tried a few different things to get this script to work
// but I think that I need to get the first or last result of the previous page
// but I don't know how to.
$get = $_GET['p'];
$pn = $_GET['pn'];
$pq = mysql_query("SELECT * FROM my_table ORDER BY id ASC LIMIT $max OFFSET $get");
// my query results appear
if(!$pn) {
$pn = 1;
}
echo "</table><br />
Page $pn of $pages<br />";
for($p = 1;$p<=$pages;$p++) {
echo "<a href='javascript:void(0);' onclick='nextPage($max, $p);' title='Page $p'>Page $p</a> ";
}
I think you have few problems there, but I try to tackle them for you. First, as comments say above, you are using code that it vulnerable to SQL injection. Take care of that - you might want to use PDO, which is as easy use as MySQL extension, and will save you from many trouble (like injection).
But to your code, lets go through it:
You should ask DB to get count of the rows, not using mysql function, it's far more effective, so use SELECT count(*) FROM mytable.
For $pages use ceil() as you want all rows to be printed, if you have $max 5 and have 11 rows, round will make $pages 2, where you actually want 3 (last page just contains that last 11th row)
in LIMIT you want to LIMIT row_count OFFSET offset. You can calculate offset from page number, so: $max = row_count but $offset = ($max * $page) - $max. In your code if $get is directly the page, it means you get $get'th row (Not sure though what happens in your JS nextpage. Bare in mind that not all use JavaScript.)
I have prepared simple example here which uses PDO, maybe that gives you idea how simple it's use PDO.
The selecting rows shows example how to put parameters in SQL, it would be perfectly safe in this case state, 'SELECT * FROM pseudorows LIMIT '.$start.','.$max by I wanted to make an example how easy it is (and then safe):
// DB config
$DB_NAME = 'test';
$DB_USER = 'test';
$DB_PASSWD = 'test';
// make connection
try {
$DB_CONN = new PDO("mysql:host=localhost;dbname=".$DB_NAME, $DB_USER, $DB_PASSWD);
$DB_CONN->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die($e);
}
// lets say user param 'p' is page, we cast it int, just to be safe
$page = (int) (isset($_GET['p'])?$_GET['p']:1);
// max rows in page
$max = 20;
// first select count of all rows in the table
$stmt = $DB_CONN->prepare('SELECT count(*) FROM pseudorows');
$stmt->execute();
if($value = $stmt->fetch()) {
// now we know how many pages we must print in pagination
// it's $value/$max = pages
$pages = ceil($value[0]/$max);
// now let's print this page results, we are on $page page
// we start from position max_rows_in_page * page_we_are_in - max_rows_in_page
// (as first page is 1 not 0, and rows in DB start from 0 when LIMITing)
$start = ($page * $max) - $max;
$stmt = $DB_CONN->prepare('SELECT * FROM pseudorows LIMIT :start,:max');
$stmt->bindParam(':start',$start,PDO::PARAM_INT);
$stmt->bindParam(':max', $max,PDO::PARAM_INT);
$stmt->execute();
// simply just print rows
echo '<table>';
while($row = $stmt->fetch()) {
echo '<tr><td>#'.$row['id'].'</td><td>'.$row['title'].'</td></tr>';
}
echo '</table>';
// let's show pagination
for($i=1;$i<=$pages;$i++) {
echo '[ '.$i.' ]';
}
}
mysql_fetch_array returns an associative array
Which means you can use reset and end to get the first and last results:
$pqa = mysql_fetch_array($pq);
$first = reset($pqa);
$last = end($pqa);
I don't see how you plan to use the actual results, just page numbers should be sufficient for pagination.
Still, hope it helps. And yes, upgrade to mysqli, so your code doesn't get obsolete.
I am coding in php and the code takes data from an array to fetch additional data from a mysql db. Because I need data from two different tables, I use nested while loops. But the code below always only prints out (echo "a: " . $data3[2]; or echo "b: " . $data3[2];) one time:
foreach($stuff as $key)
{
$query3 = "SELECT * FROM foobar WHERE id='$key'";
$result3 = MySQL_query($query3, $link_id);
while ($data3 = mysql_fetch_array($result3))
{
$query4 = "SELECT * FROM foobar_img WHERE id='$data3[0]'";
$result4 = MySQL_query($query4, $link_id);
while ($data4 = mysql_fetch_array($result4))
{
$x += 1;
if ($x % 3 == 0)
{
echo "a: " . $data3[2];
}
else
{
echo "b: " . $data3[2];
}
}
}
}
First and foremost, improve your SQL:
SELECT
img.*
FROM
foobar foo
INNER JOIN foobar_img img ON
foo.id = img.id
WHERE
foo.id = $key
You will only have to iterate through one array.
Also, it appears that you're actually only selecting one row, so spitting out one row is expected behavior.
Additionally, please prevent yourself from SQL injection by using mysql_real_escape_string():
$query3 = "SELECT * FROM foobar WHERE id='" .
mysql_real_escape_string($key) . "'";
Update: As Dan as intimated, please run this query in your MySQL console to get the result set back, so you know what you're playing with. When you limit the query to one ID, you're probably only pulling back one row. That being said, I have no idea how many $keys are in $stuff, but if it spins over once, then it will be one.
You may be better off iterating through $stuff and building out an IN clause for your SQL:
$key_array = "";
foreach($stuff as $key)
{
$key_array .= ",'" . mysql_real_escape_string($key) . "'";
}
$key_array = substr($key_array, 1);
...
WHERE foo.id IN ($key_array)
This will give you a result set with your complete list back, instead of sending a bunch of SELECT queries to the DB. Be kind to your DB and please use set-based operations when possible. MySQL will appreciate it.
I will also point out that it appears as if you're using text primary keys. Integer, incremental keys work best as PK's, and I highly suggest you use them!
You should use a JOIN between these two tables. It the correct way to use SQL, and it will work much faster. Doing an extra query inside the loop is bad practice, like putting loop-invariant code inside a loop.