I am displaying images on my website with their details, however of late the site is very slow at loading.
The main thing is this foreach loop that loops through 100 times to display 100 posts in a grid. It takes 14 seconds to run
foreach($posts as $post) {
$hashtags[] = $this->HashTagsModel->get_hashtags($post["id"]);
$author[] = $this->UserModel->get_user_details($post["user_id"]);
$comment_count[] = $this->CommentModel->get_comments_count($post["id"]);
$is_favourited[] = $this->FavouriteModel->is_favourited($post["id"]);
$is_reposted[] = $this->RepostModel->is_reposted($post["id"]);
$vote_status[] = $this->vote_status($post["id"]);
$comments[] = $this->count_comments($post["id"]);
}
How can I do this differently to make it more efficient? This worked before our websites database became massive
Any help would be appreciated,
regards,
Almost Fired
The efficient way to foreach loop query database is to not foreach query database. This is because you are allowing an unknown amount of queries to be fired off which will cause massive queueing. What if suddenly 5000 images get added, do you loop through them all? That query will take a very long time.
You have $post["id"] as your where variable I am assuming, so you could reduce this process significantly by doing a single query after formulating an array of post ids, something like this:
$postids = array();
foreach($posts as $post) {
$postids[] = $post['id'];
}
// Selecting from 1 table
$query = 'SELECT * FROM hashtags WHERE id IN ('. implode(",", $postids) .')';
This would fetch all the information on hashtags where the id is is one of your postids. That is just 1 table, you would likely want to fetch multiple, without knowing your database structure I'm going to be generic, so something like:
// Selecting and joining data from multiple tables
$query = ' SELECT author.name, table.col FROM posts
LEFT JOIN author ON author.id = post.author_id
LEFT JOIN table ON table.id = post.table_id
WHERE posts.id IN IN ('. implode(",", $postids) .')';
It is a bit more difficult to be more accuracy. I think joining tables would provide you a better result, you can even join counts for votes/comments. If that is not possible, you could query all data related to your posts and then formulate it in PHP, then you know exactly how many queries you have. For example, change your models to accept an array instead of a single post ID and then change your "WHERE post_id = x" to "WHERE post_id IN (x)" then you can do something like:
$postids = array();
foreach($posts as $post) {
$postids[] = $post['id'];
}
$hashtags = $this->HashTagsModel->get_hashtags($postids);
$author = $this->UserModel->get_user_details($postids);
$comment_count = $this->CommentModel->get_comments_count($postids);
$is_favourited = $this->FavouriteModel->is_favourited($postids);
$is_reposted = $this->RepostModel->is_reposted($postids);
$vote_status = $this->vote_status($postids);
$comments = $this->count_comments($postids);
This gets your queries outside of the loop, and you know there will only ever be 7 SQL queries, and not queries * posts. In PHP you would loop through the results of each array to assign each one back to posts based on its ID.
Related
I have three tables: "users", "posts", and "likes" almost formatted as:
For example the three table entries are:
users (two users): 1. uid: 12,
2. uid: 15.
, and
posts (three posts): 1. pid: 3, publisherId = 12, likers = 2,
2. pid: 6, publisherId = 12, likers = 0,
3. pid: 7, publisherId = 12, likers = 1.
, and
likes (three likes): 1. lid: 1, postId = 3, likerId = 12,
2. lid: 2, postId = 7, likerId = 15,
3. lid: 3, postId = 3, likerId = 15.
What I need is: To get all the posts in a multi dimensional array with an array for the unique publisher (user) and another array for the likers (users also). The output I am looking for is something like:
Array: (
post:(
pid = 3,
publisher = Array (uid = 12),
likers = Array (uid=12, uid=15)
),
post:( ....
)
).
I am already getting that with the following time consuming (I believe):
$sql = "SELECT dev_posts.* FROM posts";
if (!$result = mysql_query($sql)) die("Query failed.");
$response = array();
while($result_array = mysql_fetch_object($result)) {
$entries = array();
foreach($result_array as $key => $value) {
if ($key == "byUserId") {
$publisherID = $result_array->byUserId;
$anotherSql = "SELECT * FROM users WHERE users.uid = $publisherID";
if ($anotherResult = mysql_query($anotherSql)) {
$anothers = array();
while($anotherResult_array = mysql_fetch_object($anotherResult)) {
$another = array();
foreach($anotherResult_array as $anotherKey => $anotherValue) {
$another[$anotherKey] = $anotherValue;
}
$anothers[] = $another;
}
$entries[$key] = $anothers;
}
}
else if ($key == "likes") {
if ($value > 0){
$PID = $result_array->pid;
$anotherSql = "SELECT likes.*, users.* FROM likes LEFT JOIN users ON likes.likeUserId = users.uid WHERE $PID = likes.likePostId";
if ($anotherResult = mysql_query($anotherSql)) {
$anothers = array();
while($anotherResult_array = mysql_fetch_object($anotherResult)) {
$another = array();
foreach($anotherResult_array as $anotherKey => $anotherValue) {
$another[$anotherKey] = $anotherValue;
}
$anothers[] = $another;
}
$entries[$key] = $anothers;
}
}
else {
$entries[$key] = array();
}
}
else {
$entries[$key] = $value;
}
}
$posts[] = $entries;
}
Any suggestions are appreciated. I am still looking for join and left join solutions!
it really depends on what you're looking for:
user data for all posts:
SELECT user.*, post.*
FROM post
LEFT JOIN user ON (post.publisherid=user.id)
since it's only one publisher per post, this should give the user's data for each and every post.
liker ids
SELECT post.*, GROUP_CONCAT(likes.likerid) as likerids
FROM post
LEFT JOIN likes ON (likes.postid=post.pid)
GROUP BY post.pid
this will give you rows:
["pid" => 3, "publisherid" => 12, "likerids" => "15,17,19"]
and all you have to do in php then is:
$likerids = explode(',', $row['likerids']);
combine for fun and profit
of course, you can combine both queries into one. However, the second query only works well, if you only need the ids of likers. If you want the user data as well, it might be good, (depending on your actual use case), to collect the likerids first and fetch their user data later
SELECT *
FROM user
WHERE user.uid IN (15,17,19)
Also, you should REALLY REALLY REALLY use prepared statements to protect against sql injections. (this is not bold by accident! this is important) If you don't know what sql injections are, read it up. If anyone finds a query that's vulnerable to user provided input and sql injections, all your users' data can (and most likely will) leak into the darkness that is the internet.
Also, please use pdo or mysqli libraries for your database queries. the mysql library is deprecated and is gone in 7.[something] I believe.
update
There are a bunch of problems associated with fetching both sides of an m:n relation. I mean, essentially it's easy, just fetch it:
SELECT post.*, user.*
FROM post
LEFT JOIN likes ON (post.pid=likes.postid)
LEFT JOIN user ON (likes.likerid=user.uid)
ORDER BY post.pid
however, this will produce these rows:
pid1, publisherid1, userid1, username1
pid1, publisherid1, userid2, username2
...
pid2, publisherid2, userid1, username1
...
as you will notice, the post itself appears multiple times, once for each liker. This is a problem, which cannot be avoided by standard sql alone, because of the fundamentals of sql (being row-based).
This is essentially the data you want, but I suppose in a more aggregated form. This form also contains lots and lots of redundant data, especially assuming the post data is way bigger than the user data. To gather the data, you would have to check the pid for every row, if it's the same pid as in the row before, you somehow merge the records. ... But I would strongly advise against this approach.
I would also advise against using GROUP_CONCAT for every single field of user, although it might work. The problem is, that GROUP_CONCAT needs a delimiter, which YOU need to be different from any character in the username field (or any other field, you want to retrieve). This might or might not be a problem, but it's dirty nonetheless. In any case, you then would have to explode every of those aggregated fields in php, rebuild the users' data to build your wanted structure.
Another solution might be, to create a new field, that holds aggregated userdata as json or something, and with the intelligent use of GROUP_CONCAT and CONCAT one could create a hierarchical string for each row, that could be json itself. But this goes beyond this post. (Also I condone such use of databases that aren't made nor designed for this). There is however a JSON data type, that could be interesting ...
Ultimately, in those cases, you let the database server do the work that IMHO should be done by the client.
I would do this:
two queries, for limited number of users (because YAGNI)
first we're going to fetch the posts we want, we also add a count for likes and the publisher's user data are included as well (if you add a WHERE with data, that comes from outside the server like a browser, use prepared statements! also read up on SQL, if you don't understand all or parts of this query!) - I would assume, this is all the data you would show to a user at first. (With the power of caching, showing likers for distinct posts could be quite efficient.)
$pdo = new PDO('#yourdatabasestring#'); // rtfm!
$postresult = $pdo->query(
'SELECT p.*, '.
' pub.uid, pub.username, '.
' COUNT(likers.uid) as likecount '.
'FROM post p '.
'LEFT JOIN user as pub ON (pub.uid=post.publisherid) '.
'LEFT JOIN likes ON (post.pid=likes.postid) '.
'LEFT JOIN user as likers ON (likers.uid=likes.likerid) '
'GROUP BY p.pid '.
'LIMIT 50' // learn about offsets!!!
);
now, put all results into an array
$pids = []; // this will contain post ids for which we want to fetch likes
$posts = [];
while($post = $postresult->fetch()) {
$pids[] = $post['pid'];
$post['likers'] = []; // prepare for later
$posts[$post['pid']] = $post;
}
At this point, this array only contains the data, that was requested in the first query (post, user data of publisher). Next, we query for the likes, we use the temporarily stored post ids.*
$likers = $pdo->query(
'SELECT likes.postid, user.* '.
'FROM likes '.
'LEFT JOIN user ON (likes.likerid=user.uid) '.
'WHERE likes.postid IN ('.implode(',', $pids).')'
);
and fetching them and assigning them to the right post.
while($like = $likers->fetch()) {
$posts[$like['postid']]['likers'][] = $like;
}
now ... this solution should actually work for almost every sql database. GROUP_CONCAT doesn't provide any benefit here. Two queries are actually quite alright here. If you have a very large set of posts that you want to fetch at once, this might absolutely not be the right approach. For fairly small data sets (some hundred posts or so), this should be very much okay.
*) the WHERE clause could be replaced by WHERE postid IN ([first query with only poist.pid in select]). For certain use cases, this could be preferable.
word of advice
However, for the usual web case, I can't imagine anyone wanting to see more than 50 posts at once with already displayed userdata, likers' data and stuff. don't try to show everything at once. fetch what's necessary, try to cluster information (as I did with the $pids) to reduce the number of queries. Doing a few well-designed and short-running queries in general beats doing many queries (as in your original code), but also is more appropriate than running one huge query, where most data will (on average) be irrelevant.
I have updated my original post based on what I learned from your comments below. It is a much simpler process than I originally thought.
require '../database.php';
$pdo = Database::connect();
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = "SELECT * FROM Orders WHERE id = 430";
$q = $pdo->prepare($sql);
$q->execute(array($id));
$data = $q->fetch(PDO::FETCH_ASSOC);
echo 'Order Num: ' . $data['id'] . '<br>';
$sql = "SELECT * FROM Order_items
JOIN Parts ON Parts.id = Order_Items.part_id
WHERE Order_Items.orders_id = 430";
$q = $pdo->prepare($sql);
$q->execute(array($line_item_id));
$data = $q->fetch(PDO::FETCH_ASSOC);
while ($data = $q->fetch(PDO::FETCH_ASSOC))
{
echo '- ' . $data['part_num'] . $data['qty'] . "<br>";
}
Database::disconnect();
Unfortunately, only my first query is producing results. The second query is producing the following ERROR LOG: "Base table or view not found: 1146 Table 'Order_items' doesn't exist" but I am expecting the following results.
Expected Results from Query 1:
Order Num: 430
Expected Results from Query 2:
- Screws 400
- Plates 35
- Clips 37
- Poles 7
- Zip ties 45
Now that I understand where you are coming from, let's explain a couple of things.
1.PDO and mysqli are two ways of accessing the database; they essentially do the same things, but the notation is different.
2.Arrays are variables with multiple "compartments". Most typical array has the compartments identified by a numerical index, like:
$array[0] = 'OR12345'; //order number
$array[1] = '2017-03-15'; //order date
$array[2] = 23; //id of a person/customer placing the order
etc. But this would require us to remember which number index means what. So in PHP there are associative arrays, which allow using text strings as indexes, and are used for fetching SQL query results.
3.The statement
$data = $q->fetch(PDO::FETCH_ASSOC)
or
$row = $result->fetch_assoc()
do exactly the same thing: put a record (row) from a query into an array, using field names as indexes. This way it's easy to use the data, because you can use field names (with a little bit around them) for displaying or manipulating the field values.
4.The
while ($row = $result->fetch_assoc())
does two things. It checks if there is a row still to fetch from the query results. and while there is one - it puts it into the array $row for you to use, and repeats (all the stuff between { and }).
So you fetch the row, display the results in whatever form you want, and then loop to fetch another row. If there are no more rows to fetch - the loop ends.
5.You should avoid using commas in the FROM clause in a query. This notation can be used only if the fields joining the tables are obvious (named the same), but it is bad practice anyway. The joins between tables should be specified explicitly. In the first query you want the header only, and there is no additional table needed in your example, so you should have just
SELECT *
FROM Orders
WHERE Orders.Order_ID = 12345
whereas in the second query I understand you have a table Parts, which contains descriptions of various parts that can be ordered? If so, then the second query should have:
SELECT *
FROM Order_items
JOIN Parts ON Parts.ID = Order_Items.Part_ID
WHEERE Order_Items.Order_ID = 12345
If in your Orders table you had a field for the ID of the supplier Supplier_ID, pointing to a Suppliers table, and an ID of the person placing the order Customer_ID, pointing to a Customers table, then the first query would look like this:
SELECT *
FROM Orders
JOIN Suppliers ON Suppliers.ID = Orders.Supplier_ID
JOIN Customers ON Customers.ID = Orders.Customer_ID
WHERE Orders.Order_ID = 12345
Hope this is enough for you to learn further on your own :).
I hope someone can help with the following... I have multiple product names in a MYSQL database table on one hand and on the other hand I am getting some info such as pricing (excluding product name and some other static info) from xml feeds. The xml feed gives a product code, with which I can match the rows I need in the database because the database includes name of the product but also product code whilst xml doesn't include name of the product.
What I want to do is to fetch the name of the product and some other values from the database and link them with the other info from the xml, so that the end result is that I display the name of the products and the other info on the front-end. There are multiple products from multiple xml's...
If this was single product with a single code, I know I can use the SELECT statement with a where clause, wherein the WHERE clause is the product ID, but because I have multiple products with multiple ID's and I don't need to fetch everything from the database (but only those products displayed/searched for on the front-end), I am a bit stuck and don't know how to do this.
I hope this makes sense. If not, please do let me know.
Thanks in advance.
When you're processing the XML feed, put all the product codes into an array. Then you can construct the SQL query dynamically from this:
$codes_string = implode(', ', $product_codes);
$sql = "SELECT * FROM product_table WHERE product_id IN ($codes_string)";
I'm assuming product IDs are numeric IDs. If they're strings you'll need to put quotes around each of them:
$codes_string = implode("', '", $product_codes);
$sql = "SELECT * FROM product_table WHERE product_id IN ('$codes_string')";
You could likely use an IN query to get the products from the table. Then you'd have to merge them into an object or array of the rows you got via XML.
select * from `mydb`.`myproducts` where product_id in ('key1', 'key2', 'key3');
By your comments across the different answers, if you are looking to build your array of product codes, here's the most viable approach:
$productIds = array();
foreach ($myXmlRecords as $xmlProduct) {
array_push($productIds, $xmlProduct->product_id);
}
$query = 'select * from `mydb`.`myproducts` where product_id in (\'' . implode("', '", $productIds) . '\')';
Taking it a step farther, if you wanted to join the data of the two instead then I'd do the following:
$products = array();
$productIds = array();
foreach ($myXmlRecords as $xmlProduct) {
$p = new stdClass();
$p->product_id = $xmlProduct->product_id;
$p->xmlProp1 = $xmlProduct->prop1;
$p->xmlProp2 = $xmlProduct->prop2; // basically transfer any Xml properties you want to retain to the new $p object
array_push($products, $p);
array_push($productIds, $xmlProduct->product_id);
}
// Now build your query that will grab all matching products you got from Xml
$query = 'select * from `mydb`.`myproducts` where product_id in (\'' . implode("', '", $productIds) . '\')';
// Now match up the results
$results = mysql_query($query);
foreach ($results as $prodRow) {
foreach ($products as $p) {
if ($p->product_id == $prodRow->product_id) {
// These match, now add your db properties to the product
$p->dbProp1 = $prodRow->prop1;
$p->dbProp2 = $prodRow->prop2; // and so on
}
}
}
The end result is an array of products that have the Xml and Db info you want to combine. Before someone corrects me, there is probably a more efficient way than nested foreach statements such as updating the mysql query to use a join against the Xml data and then selecting from both that and your DB. But this is a fast approach that should get you where you need to.
Hope that helps!
I have a recursive MySQL query that returns about 25000 results, which takes a long time to load.
I would like to know if there is a way to paginate the result or to make things faster.
The code is below:
function getChildIds($id) {
echo ' <tr><td> Client No.: </td><td> ';echo $id;echo ' </td></tr> ';
$sql_query="select id from rev_r_clients WHERE parent_client_id='$id'";
$res = mysql_query($sql_query);
$ids = Array();
while($row = mysql_fetch_object($res)){
if ($row->id) {
$ids[] = $row->id;
}
}
array_walk($ids, 'getChildIds');
}
As others mentioned, the main issue is that you are running one MySQL for every user. The overhead for that can be huge so you'd better reduce the amount of queries.
One way is to query all the data in a single query and then process it recursively as you are doing. But, from the looks of it, you just want to get all children of all your users. You can do that in a very simple way with a single MySQL query:
SELECT
p.id AS parent,
GROUP_CONCAT(c.id) AS children
FROM
rev_r_clients AS p
JOIN rev_r_clients AS c ON c.parent_client_id = p.id
GROUP BY
p.id
That will provide you with every user and their children in a single query. You can get them in an array using explode afterwards (as GROUP_CONCAT will provide you a comma-separated list).
Based on your comments you just want to create a user tree. Based on the information, this is the code you could use:
# NOTE: I am using mysqli, that is what you should do as well
$res = $mysqli_conn->query(<THE_QUERY_ABOVE>);
$parents = Array();
while($row = $res->fetch_assoc()) {
$parents[$row['parent']] = explode($row['children'])
}
After that code runs, the $parent array is a mapping of IDs to an array of their children IDs that you can use for whatever you need/want. If an ID is not in the array, it means it has no children.
Ideally you would fetch the database results once and use a recursive function on the result - passing the result or part of it around - not make database calls in the recursive function itself.
That would use more memory but it would definitely speed things up.
Also, it will need to pass $limit and $offset numbers in through the next/previous button.
concern: are you handling mysql injection?
$sql_query="select id from rev_r_clients WHERE parent_client_id='$id' limit $limit offset $offset";
I have a table called news and with these two snippets of code, I want to create a search engine that scans through the keywords table. With one table connected and running very nicely, it would be cleaner to add an extra table to the query.
My task is to create a search engine that returns rows from the tables. The search engine is based on keywords and is great for specific terming, such as 'New York Times' but if I want to type in news, that's where all the news sites are ordered by id. But sometimes completely different terms that have the keyword 'news' will pop up quite high in the table unlike CNN because of the id. With a new table, it would be a lot easier to organize the tables. That way if a term entered is 'news', sites will be ordered by id and even if they clash on other tables, they are still ordered by id.
Though my query is a bit different than the traditional query, I don't know how to
add a table via a UNION or
with a LEFT JOIN tag of some sort.
My query is below and I would love for someone to explain: a) what's wrong simply b) tell me or paste the code below:
<?php
if( isset($_GET['k']) ){
$k = $_GET['k'];
}else{
$k = '';
}
$k = ( isset($_GET['k']) )? trim($_GET['k']) : '';
$terms = (strlen($k) > 0)? explode(' ', $k) : Array();
/* The code below is from a different part of the script */
$query = " SELECT * FROM scan WHERE ";
$terms = array_map('mysql_real_escape_string', $terms);
$i = 0;
foreach ($terms as $each) {
if ($i++ !== 0){
$query .= " AND ";
}
$query .= "keywords LIKE '%{$each}%'";
}
I don't know exactly what you want to do, but this might help :
$query = " SELECT * FROM scan, news WHERE scan.news_id = news.id AND ";
$terms = array_map('mysql_real_escape_string', $terms);
foreach ($terms as $each) {
$query .= "AND scan.keywords LIKE '%{$each}%'";
}
You make an union between two table by adding a condition in the query and selecting from the two tables. The condition is to ensure that the common column in the two tables are equals.
For a left join, read this http://www.w3schools.com/sql/sql_join_left.asp
I don't really know what you're asking. If you can clarify your question, I will provide an example for you. Thanks.