Using sql queries in a loop - php

I have a query in my foreach loop and I feel I should not be doing this.
How would I go about improving this code(Here I select Messages):
try{
$sql="
SELECT * FROM messages
WHERE workspace_id=:project_id
ORDER BY message_created DESC
LIMIT 20
OFFSET :offset";
$stmt=$db->prepare($sql);
$stmt->bindValue(':project_id', $project_id, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$messages=$stmt->fetchAll(PDO::FETCH_ASSOC);
}catch(Exception $e){echo $e->getMessage();}
Now I need users first and last name from table users. I couldn't think of another way but putting it in a loop.
foreach($messages as $row){
echo $row['message'];
$sql= "SELECT first_name, last_name FROM users
WHERE user_id=:id
LIMIT 1";
$stmt=$db->prepare($sql);
$stmt->bindValue(':id', $row['sent_by']);
$stmt->execute();
$user=$stmt->fetch(PDO::FETCH_ASSOC);
echo $user['first_name'] . " " . substr($user['last_name'],0,1) . ".";
}
This code works just the way I want it to but it doesn't look like it is supposed to be done this way.
Can anyone help me out here

You could do a SQL join to completely avoid the foreach loop.
try{
$sql="
SELECT messages.*, users.first_name, users.last_name
FROM messages join users on messages.user_id = users.user_id
WHERE workspace_id=:project_id
ORDER BY message_created DESC
LIMIT 20
OFFSET :offset";
$stmt=$db->prepare($sql);
$stmt->bindValue(':project_id', $project_id, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$messages=$stmt->fetchAll(PDO::FETCH_ASSOC);
}catch(Exception $e){echo $e->getMessage();}
Note: I am assuming messages table has user id and is stored in "user_id" field. Change the field name accordingly, if it is different.

Related

How to pass multiple where clause variables to bind_param?

I am trying to do a multi query using prepared statements.
I am getting number of bind param errors :
Warning: mysqli_stmt::bind_param(): Number of variables doesn't match number of parameters in prepared statement in C:\wamp\www\c\index.php on line 35
Line 35 is:
$stmt->bind_param('i', $user_id);
Here is total codes:
I tried several examples but couldnt make it work.
Example 1:
$stmt->bind_param('i', $id); //output id from session
Example 2:
$stmt->bind_param('ii',$id, $user_id);
And some more tries too.
$id = $_SESSION['id']; // value comes from session and its full
$stmt = $mysqli->prepare("SELECT users.*, stores.*, products.* FROM stores, products, users WHERE users.id = ? AND stores.user_id = ? AND products.user_id = ?");
$stmt->bind_param('i', $user_id);
$stmt->execute();
$stmt->bind_result($id, $user_id, $pname);
$stmt->fetch();
$stmt->close();
echo $pname;
When I do queries in phpmyadmin codes works fine on $sql as following 2 queries:
$sql = "SELECT users.*, stores.*, products.*\n"
. "FROM stores, products, users\n"
. "WHERE ((users.id) AND (stores.user_id) AND (products.user_id))";
AND without php code
SELECT users.*, stores.*, products.*
FROM stores, products, users
WHERE ((users.id)
AND (stores.user_id)
AND (products.user_id))
This is the closest example :
I am having error:
Warning: mysqli_stmt::bind_result(): Number of bind variables doesn't match number of fields in prepared statement in C:\wamp\www\c\index.php on line 37
Line 37 is :
$stmt->bind_result($user_id, $username, $pname);
$id = $_SESSION['id'];
Closest code :
$stmt = $mysqli->prepare("SELECT users.*, stores.*, products.* FROM stores, products, users WHERE users.id = stores.user_id AND stores.user_id = products.user_id AND products.user_id = ?");
$stmt->bind_param('i', $id);
$stmt->execute();
$stmt->bind_result($user_id, $username, $pname);
$stmt->fetch();
$stmt->close();
echo $pname;
Your prepared statement contains 3 questionmarks/placeholder. So you have to bind exactly 3 values.
I think this should work:
$stmt = $mysqli->prepare("SELECT users.*, stores.*, products.* FROM stores, products, users WHERE users.id = ? AND stores.user_id = ? AND products.user_id = ?");
$stmt->bind_param('iii', $user_id, $user_id, $user_id);
See examples at: http://php.net/manual/de/mysqli-stmt.bind-param.php
Here is working sample of the code, I had to change:
SELECT users.*, stores.*, products.* FROM
To
SELECT users.id, users.username, stores.user_id, stores.name, products.user_id, products.pname FROM
And rest of the code like so: and its working now, I wouldlike to know if its safe to use on a live and dynamic website ?
Can someone correct the code please.
Here is total working code
$id = $_SESSION['UserId'];
$stmt = $mysqli->prepare("SELECT users.id, users.username, stores.user_id, stores.name, products.user_id, products.pname
FROM stores, products, users
WHERE users.id = ?
AND stores.user_id = users.id
AND products.user_id = users.id");
$stmt->bind_param('i', $id);
$stmt->execute();
$stmt->bind_result($id, $username, $user_id, $name, $user_id, $pname);
$stmt->fetch();
$stmt->close();
echo $pname;
echo"<br>" .$name;
echo "<br>".$username;
I tried INNER JOIN AND LEFT JOIN (Mentioned in comment) but couldnt made both. work
EDİT :
Same query with INNER JOIN its working.
$sid = $_SESSION['UserId'];
$stmt = $mysqli->prepare("SELECT u.id, u.username, s.user_id, s.name, p.user_id, p.pname FROM stores AS s INNER JOIN products AS p ON p.store_id = s.store_id INNER JOIN users AS u ON u.id = p.user_id WHERE s.user_id = ?");
$stmt->bind_param('i', $sid);
$stmt->execute();
$stmt->bind_result($id, $username, $user_id, $name, $user_id, $pname);
$stmt->fetch();
$stmt->close();
echo $pname;
echo"<br>" .$name;
echo "<br>".$username;

PHP : order by clause in prepared statement

i want to use orderby clause in the prepared statement.
following is my query
$stmt = $connect->prepare("SELECT send_stamp,id,receiverid, message, time, status,sentby FROM `chat` WHERE cust_id=?");
$stmt->bind_param('i', $cust_id);
$result=$stmt->execute();
Now where do i add order by(asc) in this query to sort it by date or id.
Please help
You do it like this:
$stmt = $connect->prepare("SELECT send_stamp,id,receiverid, message, time, status,sentby FROM `chat` WHERE cust_id = ? ORDER BY id ASC");
$stmt->bind_param('i', $cust_id);
$result=$stmt->execute();
As in normal SQL query: at the end of query:
$stmt = $connect->prepare(
"SELECT
send_stamp,id,receiverid, message, time, status, sentby
FROM `chat`
WHERE cust_id=?
ORDER BY id DESC"
);
$stmt->bind_param('i', $cust_id);
$result=$stmt->execute();
You just need to add at the end of query, following is an example in which I added ascending order clause.
$stmt = $connect->prepare(
"SELECT
send_stamp,id,receiverid, message, time, status, sentby
FROM `chat`
WHERE cust_id=?
ORDER BY id ASC"
);
$stmt->bind_param('i', $cust_id);
$result=$stmt->execute();
The above query will return the record with ascending order of id, since we are using order clause on id.
Normally in mysql order by is used at the end of the statement. like
SELECT send_stamp,id,receiverid, message, time, status,sentby FROM `chat` WHERE cust_id=? order by id
you can order by id,name or anything you want just write the name of that field.
$stmt = $connect->prepare("SELECT send_stamp,id,receiverid, message, time, status,sentby FROM `chat` WHERE cust_id=? ORDER BY date ASC");

MySQL queries within a foreach loop - performance issue?

The array showcasef holds 20 items per page. I do 3 different queries within the foreach loop, which is 60 queries (just for the loop, there's additional queries too).
<?php
foreach($showcasef as $itemf){
$sf_id = $itemf['sf_id'];
$sf_url = $itemf['sf_url'];
$sf_title = $itemf['sf_title'];
$sf_urltitle = post_slug($sf_title);
// Fetch number of favs
$stmt = $conn->prepare("SELECT COUNT(f_id) FROM favourites WHERE f_showcaseid=?");
$stmt->bind_param("i", $sf_id);
$stmt->execute();
$stmt->bind_result($numfFavs);
$stmt->fetch();
$stmt->close();
// Fetch class
$stmt = $conn->prepare("SELECT avg(r_class) FROM ranks WHERE r_showcaseid=?");
$stmt->bind_param("i", $sf_id);
$stmt->execute();
$stmt->bind_result($sf_class);
$stmt->fetch();
$stmt->close();
// Fetch number of classes
$stmt = $conn->prepare("SELECT COUNT(r_class) FROM ranks WHERE r_showcaseid=?");
$stmt->bind_param("i", $sf_id);
$stmt->execute();
$stmt->bind_result($numfClasses);
$stmt->fetch();
$stmt->close();
?>
Render HTML here
<?php } ?>
Will this be a severe performance issue, or are these particular queries relatively simple? If I keep the columns indexed, should it perform okay with millions of rows (potentially)? Or can the queries be optimized/simplified?
Here's how I get the showcasef:
$stmt = $conn->prepare("SELECT s_id,s_url,s_title FROM showcase WHERE s_userid=? ORDER BY s_date DESC LIMIT $skippingFactor, 20");
$stmt->bind_param("i", $u_id);
$stmt->execute();
$stmt->bind_result($sf_id,$sf_url,$sf_title);
while($stmt->fetch())
{
$showcasef[] = [
'sf_id' => $sf_id,
'sf_url' => $sf_url,
'sf_title' => $sf_title
];
}
$stmt->close();
A few suggestions here.
Reuse prepared statements
You are creating three prepared statements inside the loop. Why don't you create your statements only once, and then reuse them using multiple binds?
<?php
$stmt1 = $conn->prepare("SELECT COUNT(f_id) FROM favourites WHERE f_showcaseid=?");
$stmt1->bind_param("i", $sf_id);
$stmt1->bind_result($numfFavs);
$stmt2 = $conn->prepare("SELECT avg(r_class) FROM ranks WHERE r_showcaseid=?");
$stmt2->bind_param("i", $sf_id);
$stmt2->bind_result($sf_class);
$stmt3 = $conn->prepare("SELECT COUNT(r_class) FROM ranks WHERE r_showcaseid=?");
$stmt3->bind_param("i", $sf_id);
$stmt3->bind_result($numfClasses);
foreach($showcasef as $itemf) {
$sf_id = ...
$stmt1->execute();
$stmt1->fetch();
/* if the fetch succeedes then $numfFavs will contain the count */
$stmt2->execute();
...
$stmt3->execute();
..
}
$stmt1->close();
$stmt2->close();
$stmt3->close();
Use a single query to Count the rows and calculate the average
You can combine the second and third statement a single SQL query:
SELECT COUNT(r_class) AS cnt, AVG(r_class) AS average
FROM ranks
WHERE r_showcaseid=?
Use a single query instead a foreach loop
With the previous suggestions you can get better performances. But are you really sure you need a foreach loop?
If your IDs are returned by another query, instead of a foreach loop is better to use a subquery:
SELECT f_showcaseid, COUNT(f_id)
FROM favourites
WHERE f_showcaseid IN (SELECT id FROM ... WHERE ...)
GROUP BY f_showcaseid
or you can provide a list of IDs to the query:
SELECT f_showcaseid, COUNT(f_id)
FROM favourites
WHERE f_showcaseid IN (?,?,?,?,?)
GROUP BY f_showcaseid
(you can dynamically create the list of ? if the number of IDs is not fixed)
You could do this in a single query I think.
Something like the following:-
SELECT f_showcaseid, COUNT(f_id), avg(r_class), COUNT(r_class)
FROM ranks WHERE r_showcaseid IN (".implode(',', $showcasef).")
GROUP BY f_showcaseid
Of course, to use parameters you would need to do that a bit more elegantly:-
<?php
$stmt = $conn->prepare("SELECT f_showcaseid, COUNT(f_id), avg(r_class), COUNT(r_class)
FROM ranks WHERE r_showcaseid IN (".implode(',', str_split(str_repeat('?', count($showcasef)), 1)).")
GROUP BY f_showcaseid");
foreach($showcasef as $itemf)
{
$stmt->bind_param("i", $itemf['sf_id']);
}
$stmt->execute();
$stmt->bind_result($numfClasses);
$stmt->fetch();
$stmt->close();
?>

PDO SQL Query Not Limiting

I'm trying to get results from a query to the database but the LIMIT isn't working. When I put LIMIT 10 it returns no results. Am I missing something here?
Here is my code. I'm trying to figure out what the reason is for this strange behavior.
$username = "derek";
$query = $conn->prepare('SELECT * FROM notifications WHERE (needs=:username OR worker=:username1) ORDER BY CASE WHEN needs=:username2 THEN needsread ELSE workerread END, time DESC LIMIT 10');
$query->bindParam(':username', $username);
$query->bindParam(':username1', $username);
$query->bindParam(':username2', $username);
$query->execute();
Ended up being something with my if statements. I was getting the results but after filtering through my if statements the 10 results I wanted to show shouldn't have shown. So after fixing my query to the database it worked. Here's my query:
$workneed = "workneed";
$follow="follow";
$query = $conn->prepare('SELECT * FROM notifications WHERE CASE WHEN needs=:username THEN type=:workneed END OR CASE WHEN worker=:username THEN type=:follow END ORDER BY CASE WHEN needs=:username THEN needsread ELSE workerread END, time DESC LIMIT 10');
$query->bindParam(':username', $username);
$query->bindParam(':workneed', $workneed);
$query->bindParam(':follow', $follow);
$query->execute();

Using two queries at the same time. PDO

Okay so, I don't really know anything about PDO, my friend just asked me to post this here since he's not very good at English. Anyway, this is how he explained it to me:
The code provided is supposed to get a couple of values, save them, and it's supposed to get something out of another table with the help of the values gotten from earlier. The problem according to my friend is that it doesn't get the second value.
Code:
$user_email = $_SESSION['user_email'];
$query = $db->prepare("SELECT username,id,password FROM user WHERE email=:email");
$query->bindParam(':email', $user_email, PDO::PARAM_INT);
$query->execute();
$row = $query->fetch();
$user_username=$row['username'];
$user_group=$row['group'];
$query_group = $db->prepare("SELECT color,name FROM group WHERE id=:id");
$query_group->bindParam(':id', $user_group, PDO::PARAM_INT);
$query_group->execute();
$row = $query_group->fetch();
$group_color=$row['color'];
$group_name=$row['name'];
The word group used as a table name needs to be enclosed in backticks. group is a reserved key word (GROUP BY clause).
SELECT
color,
name
FROM `group`
WHERE id = :id
Using the above would work.
You can shorten the entire code by using a JOIN clause too. As commented above by Prix, the code shall be:
$user_email = $_SESSION['user_email'];
$query = $db->prepare("SELECT
u.username,
u.id,
u.password,
g.color,
g.name
FROM user u
JOIN `group` g
ON g.id = u.id
WHERE u.email = :email");
// I think emails are supposed to be `PDO::PARAM_STR`
$query->bindParam(':email', $user_email, PDO::PARAM_INT);
$query->execute();
$row = $query->fetch();
$user_username = $row['username'];
$group_color = $row['color'];
$group_name = $row['name'];
You don't have group in your select statement .
If you don't use * in your select you must have the field name in your query .
$query = $db->prepare("SELECT username,id,password FROM user WHERE email=:email");
This query gives you only username,id,password back NOT the field group .
so try to use $row['group'] is wrong .
$user_group=$row['group'];
So also put group in your select statement
Place also group in backticks it's a reserved word
$query = $db->prepare("SELECT id, username, password, `group` FROM user WHERE email=:email");
This is also a reason for important variables (e.g for next query) to consider their validity.
if (isset($row['group'])) {
database logic
} else {
error
}
With this simple test you would have found the error itself.

Categories