SQL - Most efficient way to perform this query? - php

I basically have three tables, posts, images and postimages (this simply contains the ids of both the other tables and allows more than one image in each post and each image to be reused in multiple posts if necessary).
I'm trying to list the titles of all the posts, along with a single, small image from each (if one exists)
I can do it with the following:
$sql ="SELECT * FROM posts p, images im, postimages pi WHERE
AND pi.post_id = p.id
AND pi.image_id = im.image_id
ORDER BY created_at LIMIT 10";
But obviously, if there is more than one image, the record is displayed twice.
I know I could probably do it with a subquery, but that seems horribly inefficient. Is there a more elegant way around this?
Any advice would be appreciated. Thanks.

select
*
from
posts p
left outer join (select post_id, max(image_id) as image_id
from postimages group by post_id) pi on
p.id = pi.post_id
left outer join images im on
pi.image_id = im.image_id
You can do the subquery so that it only has to be executed once, not per row. This way, it's used as a subquery for an entire table, and then joined to the posts. Obviously, this can be MIN(image_id) if you want to take the first image. Really, whatever you prefer.
Edit: Made it be a left outer join to capture even those images that don't exist in the posts. This will return null for the image in the case that it happens to be non-existant.

SELECT p.id,p.title,im.src
FROM posts p
LEFT JOIN postimages pi
ON p.id = pi.post_id
LEFT JOIN images im
ON pi.image_id = im.image_id
GROUP BY p.id
ORDER BY created_at
LIMIT 10
Not sure if this is the most efficient, you will have to run this against a inner query.
It will work only for MySql as far as I know.

Related

Subquery to pull out multiple images for multiple products

Here's what the table look like:
I'm trying to do a subquery that will pull each image name out for each product. The subquery I got is only pulling the first one out. It may be that I've been looking at it too long or whatever, either way, can someone tell me what I'm doing wrong?
Select c.field_id_33 AS email, o.order_id,c.field_id_76 AS pics, Group_concat(DISTINCT o.entry_id) AS Products,group_concat(DISTINCT t.title),(SELECT field_id_76 FROM finn_channel_data WHERE entry_id = o.entry_id), group_concat(DISTINCT t.url_title) from finn_cartthrob_order_items o
LEFT JOIN finn_channel_data c
ON c.entry_id=o.order_id
LEFT JOIN finn_channel_titles t
ON o.entry_id=t.entry_id
GROUP BY email

MySql - Joining another table with multiple rows, inserting a query into a another query?

I've been racking my brain for hours trying work out how to join these two queries..
My goal is to return multiple venue rows (from venues) based on certain criteria... which is what my current query does....
SELECT venues.id AS ven_id,
venues.venue_name,
venues.sub_category_id,
venues.score,
venues.lat,
venues.lng,
venues.short_description,
sub_categories.id,
sub_categories.sub_cat_name,
sub_categories.category_id,
categories.id,
categories.category_name,
((ACOS( SIN(51.44*PI()/180)*SIN(lat*PI()/180) + COS(51.44*PI()/180)*COS(lat*PI()/180)*COS((-2.60796 - lng)*PI()/180)) * 180/PI())*60 * 1.1515) AS dist
FROM venues,
sub_categories,
categories
WHERE
venues.sub_category_id = sub_categories.id
AND sub_categories.category_id = categories.id
HAVING
dist < 5
ORDER BY score DESC
LIMIT 0, 100
However, I need to include another field in this query (thumbnail), which comes from another table (venue_images). The idea is to extract one image row based on which venue it's related to and it's order. Only one image needs to be extracted however. So LIMIT 1.
I basically need to insert this query:
SELECT
venue_images.thumb_image_filename,
venue_images.image_venue_id,
venue_images.image_order
FROM venue_images
WHERE venue_images.image_venue_id = ven_id //id from above query
ORDER BY venue_images.image_order
LIMIT 1
Into my first query, and label this new field as "thumbnail".
Any help would really be appreciated. Thanks!
First of all, you could write the first query using INNER JOIN:
SELECT
...
FROM
venues INNER JOIN sub_categories ON venues.sub_category_id = sub_categories.id
INNER JOIN categories ON sub_categories.category_id = categories.id
HAVING
...
the result should be identical, but i like this one more.
What I'd like to do next is to JOIN a subquery, something like this:
...
INNER JOIN (SELECT ... FROM venue_images
WHERE venue_images.image_venue_id = ven_id //id from above query
ORDER BY venue_images.image_order
LIMIT 1) first_image
but unfortunately this subquery can't see ven_id because it is evaluated first, before the outer query (I think it's a limitation of MySql), so we can't use that and we have to find another solution. And since you are using LIMIT 1, it's not easy to rewrite the condition you need using just JOINS.
It would be easier if MySql provided a FIRST() aggregate function, but since it doesn't, we have to simulate it, see for example this question: How to fetch the first and last record of a grouped record in a MySQL query with aggregate functions?
So using this trick, you can write a query that extracts first image_id for every image_venue_id:
SELECT
image_venue_id,
SUBSTRING_INDEX(
GROUP_CONCAT(image_id order by venue_images.image_order),',',1) as first_image_id
FROM venue_images
GROUP BY image_venue_id
and this query could be integrated in your query above:
SELECT
...
FROM
venues INNER JOIN sub_categories ON venues.sub_category_id = sub_categories.id
INNER JOIN categories ON sub_categories.category_id = categories.id
INNER JOIN (the query above) first_image on first_image.image_venue_id = venues.id
INNER JOIN venue_images on first_image.first_image_id = venue_images.image_id
HAVING
...
I also added one more JOIN, to join the first image id with the actual image. I couldn't check your query but the idea is to procede like this.
Since the query is now becoming more complicated and difficult to mantain, i think it would be better to create a view that extracts the first image for every venue, and then join just the view in your query. This is just an idea. Let me know if it works or if you need any help!
I'm not too sure about your data but a JOIN with the thumbnails table and a group by on your large query would probably work.
GROUP BY venues.id

mysql/php: show posts and for each post all comments

I know this question has been asked multiple times (however, I could still not find a solution):
PHP MYSQL showing posts with comments
mysql query - blog posts and comments with limit
mysql structure for posts and comments
...
Basic question: having tables posts, comments, user... can you with one single select statement select and show all posts and all comments (with comment.user, comment.text, comment.timestamp)? How would such a select statement look like? If not, what is the easiest solution?
I also tried to JOIN the comments table with the posts table and use GROUP BY, but I got either only one comment in each row or each comment but also those posts multiple times!?
I tried the solution of the first link (nested mysql_query and then fetch) as well as the second link (with arrays). However, the first caused a bunch of errors (the syntax in that post seems to be not correct and I could not figure out how to solve it) and in the second I had problems with the arrays.
My query looks like this till now:
SELECT p.id, p.title, p.text, u.username, c.country_name, (SELECT SUM(vote_type) FROM votes v WHERE v.post_id = p.id) AS sum_vote_type FROM posts p LEFT JOIN user u ON ( p.user_id = u.id ) LEFT JOIN countries c ON ( c.country_id = u.country_id ) ORDER BY $orderby DESC
I was wondering if this issue was not very common, having posts and comments to show...?
Thank you for every help in advance!
Not knowing your database structure, it should look something like this. Note that you should replace the * characters with more explicit lists of columns you actually need.
SELECT p.*, c.*, u.* FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
LEFT JOIN users u ON u.id = p.author_id
Note that if you're just trying to get counts, sums and things like that it's a good idea to cache some of that information. For instance, you may want to cache the comment count in the post table instead of counting them every query. Only count and update the comment count when adding/removing a comment.
EDIT:
Realized that you also wanted to attach user data to each comment. You can JOIN the same table more than once but it gets ugly. This could turn into a really expensive query. I also am including an example of how to alias columns so it's less confusing:
SELECT p.*, c.*, u.name as post_author, u2.name as comment_author FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
LEFT JOIN users u ON u.id = p.author_id
LEFT JOIN users u2 ON u2.id = c.author_id

Query to show all blog posts while linking in categories

So I have a query that should get all posts from a table while linking the posts categories and the user that created the post. What I am getting though is just a single post returned, not all posts. Below is the schema:
Posts
=====
id
Categories
==========
id
Post categories
===============
postID
categoryID
And here is the SQL code I have so far, kinda deep but it gets all categories concatenated into a single field.
SELECT
blgpostcategories.*,
blgcategories.id,
GROUP_CONCAT(blgcategories.name) AS categories,
blgposts.*,
users.firstName,
users.id AS usersId,
users.lastName,
users.email
FROM blgposts
RIGHT OUTER JOIN blgpostcategories
ON blgposts.id = blgpostcategories.postID
RIGHT OUTER JOIN blgcategories
ON blgpostcategories.categoryID = blgcategories.id
INNER JOIN users
ON blgposts.userID = users.id
UPDATED Query from JNK - Still only returning a single row :-(
SELECT
blgpostcategories.*,
blgcategories.id,
GROUP_CONCAT(blgcategories.name) AS categories,
blgposts.*
FROM blgposts
LEFT OUTER JOIN blgpostcategories
ON blgposts.id = blgpostcategories.postID
LEFT OUTER JOIN blgcategories
ON blgpostcategories.categoryID = blgcategories.id
In an answer by Adam Robinson to a similar question
Because you're using an aggregate in
your query (GROUP_CONCAT), your query
is being grouped. Since you have no
group by clause, your group is the
entire result set (hence seeing every
tag the author has used). Because
MySQL allows for using non-grouped
columns in grouped statements, you
aren't getting an error, but you
aren't getting the query that you
want.
In order to retrieve the proper
results, you need to group your query
on thread.id.
In your case just adding GROUP BY blgcategories.id should do it
Two things I see right off the bat:
1 - Do a LEFT OUTER JOIN not a RIGHT. RIGHT means "show me all the stuff in the right table, whether or not I have anything matching in the left table." You want everything from blogposts so do a left.
2 - Your INNER JOIN may be an issue as well. Are you sure users is populated fully?
EDIT:
The issue is you are using an aggregate function without a GROUP BY! Take out the GROUP_CONCAT() and it should work fine.

How can I join 3 tables with mysql & php?

I have a page that pulls the users Post,username,xbc/xlk tags etc which is perfect... BUT since I am pulling information from a MyBB bulletin board system, its quite different. When replying, people are are allowed to change the "Thread Subject" by simplying replying and changing it.
I dont want it to SHOW the changed subject title, just the original title of all posts in that thread.
By default it repies with "RE:thread title". They can easily edit this and it will show up in the "Subject" cell & people wont know which thread it was posted in because they changed their thread to when replying to the post.
So I just want to keep the orginial thread title when they are replying.
Make sense~??
Tables:mybb_users
Fields:uid,username
Tables:mybb_userfields
Fields:ufid
Tables:mybb_posts
Fields:pid,tid,replyto,subject,ufid,username,uid,message
Tables:mybb_threads
Fields:tid,fid,subject,uid,username,lastpost,lastposter,lastposteruid
I haev tried multiple queries with no success:
$result = mysql_query("
SELECT * FROM mybb_users
LEFT JOIN (mybb_posts, mybb_userfields, mybb_threads)
ON (
mybb_userfields.ufid=mybb_posts.uid
AND mybb_threads.tid=mybb_posts.tid
AND mybb_users.uid=mybb_userfields.ufid
)
WHERE mybb_posts.fid=42");
$result = mysql_query("
SELECT * FROM mybb_users
LEFT JOIN (mybb_posts, mybb_userfields, mybb_threads)
ON (
mybb_userfields.ufid=mybb_posts.uid
AND mybb_threads.tid=mybb_posts.tid
AND mybb_users.uid=mybb_posts.uid
)
WHERE mybb_threads.fid=42");
$result = mysql_query("
SELECT * FROM mybb_posts
LEFT JOIN (mybb_userfields, mybb_threads)
ON (
mybb_userfields.ufid=mybb_posts.uid
AND mybb_threads.tid=mybb_posts.tid
)
WHERE mybb_posts.fid=42");
Your syntax isn't appropriate for carrying out multiple LEFT JOINs. Each join needs its own ON clause.
SELECT
*
FROM
mybb_users
LEFT JOIN mybb_userfields ON mybb_users.uid = mybb_userfields.ufid
LEFT JOIN mybb_posts ON mybb_userfields.ufid = mybb_posts.uid
LEFT JOIN mybb_threads ON mybb_posts.tid = mybb_threads.tid
WHERE
mybb_posts.fid = 42
This query should give the results you want. But it may not be the most efficient query for getting those results. Check the output of EXPLAIN as part of testing, to make sure it is not using table scans or anything like that.
Do all of these joins need to be LEFT JOINs? LEFT JOIN forces MySQL to join the tables in the indicated order, rather than allowing the query optimiser to determine the best order in which to join them. That's why you might need to be careful about the query execution plan. The main difference between JOIN and LEFT JOIN as far as query output is concerned is that LEFT JOIN resultsets will contain at least one row for each row of the table on the left-hand side of the join, whereas a regular JOIN will not contain a row if there aren't matches on the right-hand side of the join.
Edit: Also, you say that "I don't want it to SHOW the changed subject title, just the original title of all posts in that thread." This suggests that you only want a subset of the columns from these tables, in which case SELECT * is inappropriate.

Categories