How to left join multiple one to many tables in mysql? - mysql

i have a problem with joining three tables in mysql.
lets say we have a table named posts which I keep my entries in it, i have a table named likes which i store user_id's and post_id's in and a third table named comments which i store user_id's and post_id's and comment's text in it.
I need a query that fetches list of my entries, with number of likes and comments for each entry.
Im using this query:
SELECT posts.id, count(comments.id) as total_comments, count(likes.id) as total_likes
FROM `posts`
LEFT OUTER JOIN comments ON comments.post_id = posts.id
LEFT OUTER JOIN likes ON likes.post_id = posts.id
GROUP BY posts.id
but there is a problem with this query, if comments are empty for an item, likes count is just ok, but lets say if an entry has 2 comments and 4 likes, both total_comments and total_likes will be "8", meaning that mysql multiplies them.
I'm confused and I dont know what whould I do.
Thanks in advace.

Use count(distinct comments.id) and count(distinct likes.id), provided these ids are unique.

Well this is one way to approach it (assuming mysql allows derived tables):
SELECT posts.id, comments.total_comments, likes.total_likes
FROM `posts`
LEFT OUTER JOIN (select post_id, count(id) as total_comments from comments) comments
ON comments.post_id = posts.id
LEFT OUTER JOIN (select post_id, count(id) as total_likes from likes) likes
ON likes.post_id = posts.id
You could also use correlated subqueries. You may want a case statment inthere to account for putting in a 0 when there are no matched records.
Let's try a correlated subquery:
SELECT posts.id,
(select count(Id) from comments where post_id = posts.id) as total_comments,
(select count(Id) from likes where post_id = posts.id) as total_likes
FROM `posts`

Related

I want to get all posts which has more than 250 likes(likes are stored in different tabe)

I have two mysql database table's posts and post_likes...I want to get all posts which has more than 250 likes
right now i am using this query :-
SELECT posts.*, #total_likes := COUNT(nft_likes.id) as total_likes FROM posts inner join nft_likes on nft_likes.nft_id=posts.auction_id where #total_likes>1 group by posts.id
This is the first time i have asked a question.so pls forgive for bad way of telling
post_likes table schema
post table schema
In the WHERE clause you can only refer to a row's data. The result of a COUNT, however, refers to the aggregation of several rows. Use the HAVING clause for limitations on these results.
SELECT
p.*,
COUNT(l.id) AS total_likes
FROM posts p
INNER JOIN nft_likes l ON l.nft_id = p.auction_id
GROUP BY p.id
HAVING COUNT(l.id) > 1
ORDER BY p.id;

How do I properly combine these SELECTS (MySQL)?

I want to get all my posts data to show it on my webpage with this SELECT. I have my table posts that contains most of the posts (+ replies) data and a table social that tracks who views and likes it(each like is a new row).
Normally I can get the username, post time, content... but I'm struggling to get the number of views the post gets, the number of likes, and the number of replies in the same SELECT. My base SELECT looks like this:
SELECT posts.username, posts.time, cat.cat_name,
posts.title, posts.content, posts.reply,
posts.user_file, posts.audio, social.id,
social.views, social.likes
FROM posts
LEFT JOIN user on posts.user_id = user.id
LEFT JOIN cat ON posts.cat_id = cat.id
LEFT JOIN social ON posts.id = social.post_id
If I wanted to get the number of comments per post I would use
(if the value inside reply is 0 it's a post if it's a reply it contains the post id it's referring to):
SELECT COUNT(*) AS `comments` FROM posts GROUP BY reply
/* this returns an error: SQL Error (1242): Subquery returns more than 1 row */
And I would get the number of likes and views like this:
SELECT MAX(social.views) AS views FROM social GROUP BY post_id
SELECT social.likes FROM social WHERE social.id = (SELECT MAX(social.id) FROM social GROUP BY post_id
But if I use it together in the earlier SELECT it just fills every row with the same number. Example:
... posts.audio, social.id, (SELECT MAX(social.views) AS views FROM social GROUP BY post_id) FROM posts ...
This just fills every row even if it shouldn't have views with 25 (correct value for 1 specific row but wrong for everything else).
What would be a proper way of making a bigger SELECT like this?
Not sure if it matters but I am using it with a MySQL module in NodeJS.
Try this way
SELECT posts.username, posts.time, cat.cat_name,
posts.title, posts.content, posts.reply,
posts.user_file, posts.audio,
COUNT(social.views), COUNT(social.likes)
FROM posts
LEFT JOIN user on posts.user_id = user.id
LEFT JOIN cat ON posts.cat_id = cat.id
LEFT JOIN social ON posts.id = social.post_id
GROUP BY
posts.username, posts.time, cat.cat_name,
posts.title, posts.content, posts.reply,
posts.user_file, posts.audio
This is the closest I've gotten to SELECTing everything in one query but I still don't have a way of counting the number of comments:
SELECT posts.username, posts.time, cat.cat_name,
posts.title, posts.content, posts.reply,
posts.user_file, posts.audio,
social.views, social.likes
FROM posts
LEFT JOIN user on posts.user_id = user.id
LEFT JOIN cat ON posts.cat_id = cat.id
LEFT JOIN social ON posts.id = social.post_id
WHERE social.likes IN (SELECT social.likes FROM social
WHERE social.id IN (SELECT MAX(social.id)
FROM social GROUP BY post_id))
GROUP BY social.post_id
HAVING posts.reply = 0

Can you use Count(*) in an inner join?

Currently I have the following database:
Table 1: Customer_Stores
unique_id
page_address
date_added
guide_summary
user_name
cover_photo
guide_title
Table 2: Customer_Stories_Likes
story_id
likex
The 'like' column in the second table contains a 1 or a 0 to indict whether or not a user has liked a post.
What I'd like to do is join these two tables together with 'post_id' and count all of the 'likes' for all the posts based on post_id and order these by how many likes each post got. Is this possible with a single statement? or is it better to use a Count(*) to first determine how many likes each post has?
Yes, it's possible, but you don't need an inner join, because you don't actually need the posts table to do it.
SELECT post_id, count(like) AS post_likes
FROM likes
WHERE like = 1
GROUP BY post_id
ORDER BY post_likes DESC
If you need other information from the posts table as well, you could join it to a subquery that gets the like counts.
SELECT posts.*, like_count
FROM
posts LEFT JOIN
(SELECT post_id, count(like) AS like_count
FROM likes
WHERE like = 1
GROUP BY post_id) AS post_likes
ON posts.post_id = post_likes.post_id
ORDER BY like_count DESC
I used LEFT JOIN rather than INNER JOIN, you can use INNER JOIN if you don't want to include posts with no likes.

Nested query performance

I have two queries below. The first one has a nested select. The second one makes use of a group by clause.
select
posts.*,
(select count(*) from comments where comments.post_id = posts.id and comments.is_approved = 1) as comments_count
from
posts
select
posts.*,
count(comments.id) comments_count
from
posts
left join comments on
comments.post_id = posts.id
group by
posts.*
From my understanding the first query is worse because it has to do a select for each record in posts where as the second query does not.
Is this true or false?
As with all performance questions, you should test the performance on your system with your data.
However, I would expect the first to perform better, with the right indexes. The right index for:
select p.*,
(select count(*)
from comments c
where c.post_id = p.id and c.is_approved = 1
) as comments_count
from posts p
is comments(post_id, is_approved).
MySQL implements a group by by doing a file sort. This version saves a file sort on all the data. My guess is that will be faster than the second method.
As a note: group by posts.* is not valid syntax. I assume this was intended for illustration purposes only.
This is the standard way I would do it (the use of LEFT JOIN, and SUM lets you also know which posts have no comments.)
SELECT posts.*
, SUM(IF(comments.id IS NULL, 0, 1)) AS comments_count
FROM posts
LEFT JOIN comments USING (post_id)
GROUP BY posts.post_id
;
But if I were trying for faster, this might be better.
SELECT posts.*, IFNULL(subQ.comments_count, 0) AS comments_count
FROM posts
LEFT JOIN (
SELECT post_id, COUNT(1) AS comments_count
FROM comments
GROUP BY post_id
) As subQ
USING (post_id)
;
After a bit more research I found no time difference between the two queries
Benchmark.bm do |b|
b.report('joined') do
1000.times do
ActiveRecord::Base.connection.execute('
select
p.id,
(select count(c.id) from comments c where c.post_id = p.id) comment_count
from
posts l;')
end
end
b.report('nested') do
1000.times do
ActiveRecord::Base.connection.execute('
select
p.id,
count(c.id) comment_count
from
posts File.join(File.dirname(__FILE__), *%w[rel path here])
left join comments c on
c.post_id = p.id
group by
p.id;')
end
end
end
user system total real
nested 2.120000 0.900000 3.020000 ( 3.349015)
joined 2.110000 0.990000 3.100000 ( 3.402986)
However I did notice that when running an explain for both queries, more indexes are possible in the first query. Which makes me think it is a better option if the attributes needed in the select changed.

getting the maxium count of comments in terms of users using mysql?

i have this 4 tables:
posts{id,post,date}
comment{id, user_id,post_id, comment, date}
tag_post{tag_id,post_id}
users{user_id, email,pwd,username}
i want to make this complex query, i want to get the maxiumum number of commenters(users) from a certain topic:
i.e.
select the most commeneters(count) on posts that have been tagged with tag_id=39
LIMIT 5
thanks :))
What about something like this :
select users.user_id, count(*) as nb_comments
from posts
inner join tag_posts on tag_posts.post_id = posts.id
inner join comment on comment.post_id = posts.id
inner join users on users.user_id = comment.user_id
where tag_posts.tag_id = 39
group by users.user_id
order by count(*) desc
limit 5
It should get you the five users who commented the most on posts that have the tag 39.