Help building a sql query that uses inner join - mysql

I am trying to build a sql query that I think it involves inner joins, but I can't figure it out. Here's the model:
There's two tables: comments, posts
Among many columns, there's the following important ones: comments.id, comments.user_id (owner), comments.post_id (reference to posts table), posts.id, posts.editor_id (which is the person, ie, owner of post).
I want to get the comments that either
1) current user has written, so something like:
select * from comments where user_id = <<current_user_id>>
2) (Assume current_user is editor). Get all comments that belong to a post that you have created.
This is what I have, but I get multiple lines....what am I missing?
select * FROM comments INNER JOIN posts ON comments.post_id = <<test_id>>
WHERE posts.editor_id = <<current_user.editor_id>>;
If you could give me a sql query that includes both of these things, that would be amazing.
Thanks!

Im not sure what you mean by "multiple lines" as you would return a row for each comment.
This should do it.
SELECT comments.*
FROM comments
INNER JOIN posts
ON comments.post_id = posts.id
WHERE posts.editor_id = #editorID;

I want to get the comments that either 1) current user has written, so something like: select * from comments where user_id = <>
That's correct. What's wrong with that?
Get all comments that belong to a post that you have created. This is what I have, but I get multiple lines
Well... you're supposed to. Looking at the schema it's indeed a post could contain many comments. The query given by tyrongower should do what you want.

Going with your requirements, this should work for you:
SELECT * FROM comments
WHERE (user_id = 1)
OR (post_id IN (SELECT id FROM post WHERE editor_id = 1))
In the above query "1" is used as a sample and should be replace with the id of current user.

Related

Filter out records with at least one association that doesn't meet given conditions

Let's say I have posts and categorizations.
Post(id)
Categorization(post_id, topic_id)
I'd like to fetch posts that don't belong to a specific topic id.
In my case I have to use an inner join when joining Post to Categorizations as i have other filters to execute.
How do I go about this?
I have tried the following:
Post.joins(:categorizations).where("categorizations.topic_id != ?", doomed_topic_id)
But this returns posts that still have OTHER topics. it only works with posts with just one single topic that happens to be the unwanted one.
For instance, if I have a post with 2 categories (the doomed topic_id AND another topic) this query fails and actually fetches it, instead of filtering it out.
Try:
Posts.where('not exists
(select * from categorizations
where post_id = posts.id and
topic_id = ?)', doomed_id)

MySQL select all posts + additional data in different groups

I'm trying to select all posts from a specific category along with some additional data from another table.
Despite my efforts none of the sql queries that I've tried have worked out so far.
Here is my code.
SELECT
a.*,
b.*
FROM forum_posts AS a
LEFT JOIN forum_categories AS b ON a.post_category = b.category_id
WHERE category_slug = 'news-and-anouncements'
Here's a screenshot to illustrate the result I'm trying to achieve.
As you can see I'm grabbing posts from the posts table, but I also wish to get the category fields without re-selecting them in each post selection, if that makes sense.
Any ideas on how I should tweak the query?

Retrieve first few comments on a post from MySQL table

You know how the Facebook home feed lists all the recent posts? It shows the user that posted, their actual post and then the first few comments attached to that post. That's what I'm trying to achieve, but I'm having a hard time building a single query that can gather all that data.
I have 3 tables: Uses, Posts and Comments. Each has a unique ID, but they reference each other's IDs. i.e, the Comments table has columns for the user_id of the user who posted and the post_id of the post it is attached to.
At the minute I'm querying SQL to gather all the posts. I join my Users and Comments tables to learn the Username of the poster and a total of how many comments the post has, like so:
$query = "
SELECT `posts`.`id`,`posts`.`message`,`posts`.`link`,
`posts`.`posted`,`posts`.`category`,`posts`.`user_id`,
`users`.`username`,
count(`comments`.`id`)
FROM `posts`
INNER JOIN `users`
ON `posts`.`user_id`=`users`.`id`
JOIN `comments`
ON `comments`.`post_id`=`posts`.`id`
WHERE `posts`.`group_id` = '$id'
AND `posts`.`category`='$filter'
GROUP BY `posts`.`id`
ORDER BY `posts`.`posted`
DESC
";
But instead of finding how many comments a post has, I would instead like to read the first few posts. Can anyone think of a way to achieve this with just the one query?
You can use the LIMIT clause to the the "first" posts. By making the "first posts" a subquery and then joining in the comments you can get everything in a single query. The comments should be left-joined in case of posts with no comments.
Notes:
This query is untested, but it should be close.
This will get all comments for the first few posts, so you'll need to limit the display of "max 3 comments per post" using the front-end display code.
It may be possible to limit comments to 3 per post within this query using variables, but that's not something I know how to do.
Here's the query:
SELECT
FirstPosts.id,
FirstPosts.message,
FirstPosts.link,
FirstPosts.posted,
FirstPosts.posts,
FirstPosts.user_id,
FirstPosts.username,
comments.<< your comment column >>
FROM (
SELECT `posts`.`id`,`posts`.`message`,`posts`.`link`,
`posts`.`posted`,`posts`.`category`,`posts`.`user_id`,
`users`.`username`
FROM `posts`
INNER JOIN `users`
ON `posts`.`user_id`=`users`.`id`
WHERE `posts`.`group_id` = '$id'
AND `posts`.`category`='$filter'
ORDER BY `posts`.`posted` DESC
LIMIT 20) FirstPosts
LEFT JOIN comments ON FirstPosts.id = comments.post_id
ORDER BY FirstPosts.Posted, comments.<< column you use to determing comment order >>
If you determine comment order by a date or sequence, you'll have to ORDER BY FirstPosts.Posted, comments.whatever DESC.
Hope this helps!

Select a post that does not have a particular tag

I have a post/tag database, with the usual post, tag, and tag_post tables. The tag_post table contains tagid and postid fields.
I need to query posts. When I want to fetch posts that have a certain tag, I have to use a join:
... INNER JOIN tag_post ON post.id = tag_post.postid
WHERE tag_post.tagid = {required_tagid}`
When I want to fetch posts that have tagIdA and tagIdB, I have to use two joins (which I kind of came to terms with eventually).
Now, I need to query posts that do not have a certain tag. Without much thought, I just changed the = to !=:
... INNER JOIN tag_post ON post.id = tag_post.postid
WHERE tag_post.tagid != {certain_tagid}`
Boom! Wrong logic!
I did come up with this - just writing the logic here:
... INNER JOIN tag_post ON post.id = tag_post.postid
WHERE tag_post.postid NOT IN
(SELECT postid from tag_post where tagid = {certain_tagid})
I know this will work, but due to the way I've been brought up, I feel guilty (justified or not) whenever I write a query with a subquery.
Suggest a better way to do this?
You can think of it as "find all rows in posts that do not have a match in tags (for a specific tag)"
This is the textbook use case for a LEFT JOIN.
LEFT JOIN tag_post ON post.id = tag_post.postid AND tag_post.tagid = {required_tagid}
WHERE tag_post.tag_id IS NULL
Note that you have to have the tag id in the ON clause of the join.
For a reference on join types, see here: http://www.codinghorror.com/blog/2007/10/a-visual-explanation-of-sql-joins.html
In addition to Gavin Towey's good answer, you can use a not exists subquery:
where not exists
(
select *
from tag_post
where post.id = tag_post.postid
and tag_post.tagid = {required_tagid}
)
The database typically executes both variants in the same way. I personally find the not exists approach easier to read.
When I want to fetch posts that have tagIdA and tagIdB, I have to use two joins (which I kind of came to terms with eventually).
There are other ways.
One can obtain all the id of all posts that are tagged with both tagid 123 and 456 by grouping filtering tag_post for only those tags, grouping by post and then dropping any groups that contain fewer tags than expected; then one can use the result to filter the posts table:
SELECT * FROM posts WHERE id IN (
SELECT postid
FROM tag_post
WHERE tagid IN (123,456)
GROUP BY postid
HAVING COUNT(*) = 2
)
If a post can be tagged with the same tagid multiple times, you will need to replace COUNT(*) with the less performant COUNT(DISTINCT tagid).
Now, I need to query posts that do not have a certain tag.
This is known as an anti-join. The easiest way is to replace IN from the query above with NOT IN, as you proposed. I wouldn't feel too guilty about it. The alternative is to use an outer join, as proposed in #GavinTowey's answer.

MySQL LIMIT 1 on second table with INNER JOIN

I am using this query to print out a forum board and all it's sub forums. What happens, as may be expected, is that all posts in all threads belonging to that forum are displayed. What I would like to happen is only the first post from each thread is displayed along with the forum title.
Query:
SELECT tf_threads.*, tf_posts.*
FROM tf_threads INNER JOIN tf_posts
ON tf_threads.thread_id=tf_posts.thread_id
AND tf_threads.parent_id=54 ORDER BY tf_posts.date ASC
Please note the parent_id field is given a variable in the real query.
So. If I make sense, can anyone help me out as to what query to write to only select the first post from each thread?
If there are no simple(ish) answers, how could I do it if I used a post number field in the second table, for example, the first post in thread has number 1, second post has number 2, etc. If I use this method, I'd obviously only like to select posts with a count number field of 1. I could just expand the original query with a AND post_number=1 right?
Thanks for reading,
James
Something like this?
http://murrayhopkins.wordpress.com/2008/10/28/mysql-left-join-on-last-or-first-record-in-the-right-table/
Edit: I think that it has to be something like this, but I'm also not an SQL expert:
SELECT tf_threads.*, tf_posts_tmp.*
FROM tf_threads
LEFT JOIN (SELECT p1.*
FROM tf_posts as p1
LEFT JOIN tf_posts AS p2
ON p1.postid = p2.postid AND p1.date < p2.date) as tf_posts_tmp
ON (tf_threads.thread_id=tf_posts_tmp.thread_id)