Joining three tables with 2 count function - mysql

I've been at it for a day and couldn't really figure it out. I'm making a page where users can post and other users can reply and like.
I have these three tables: posts, replies, and likes. FYI that they all have a different number of rows, see full details below.
Posts table
post_id message
------- -------
1 This is post #1
2 This is post #2
3 This is post #3
Replies table
reply_id post_id message
------- ------- -------
1 1 This is a reply to post #1
2 1 This is a reply to post #1
3 2 This is a reply to post #2
4 2 This is a reply to post #2
5 3 This is a reply to post #3
Likes table
like_id post_id liked
------- ------- -------
1 1 Yes
2 1 Yes
3 1 Yes
4 2 Yes
5 2 Yes
6 3 Yes
7 3 Yes
Those are the structure of my tables. What I need to achieve is like this below:
All tables joined and tallied
post_id total_replies total_likes
------- ------------- -----------
1 2 3
2 2 2
3 1 2
Basically, for the 1st post, it should show that it has 2 replies and 3 likes. I cannot seem to do it using two counts. It is giving me incorrect numbers.
select posts.post_id, count(replies.post_id) as total_replies, count(likes.post_id) as total_likes from posts
inner join replies on posts.post_id = replies.post_id
inner join likes on posts.post_id = likes.post_id
group by posts.post_id

I would suggest correlated subqueries:
select p.post_id,
(select count(*)
from replies r
where r.post_id = p.post_id
) as total_replies,
(select count(*)
from likes l
where l.post_id = p.post_id
) as total_likes
from posts p;
The problem with your query is that you are joining along two different dimensions, so you are getting a Cartesian product -- all likes and all replies for a given post.
Not only does this get rid of that problem, but with indexes on replies(post_id) and likes(post_id) this should have better performance than any solution that does an aggregation over all the data.

I took your query and made a small edit so that it produces desired output. However note that the query would not be efficient. Gordon’s solution is more effective in terms of efficiency.
select
posts.post_id,
count(distinct replies.reply_id) as total_replies,
count(distinct likes.like_id) as total_likes
from posts
inner join replies on posts.post_id = replies.post_id
inner join likes on posts.post_id = likes.post_id
group by
posts.post_id;
Or would be better to pre-aggregate your metrics. That way it would be bit more efficient.
select
posts.post_id,
r.total_replies,
L.total_likes
from posts
inner join (Select post_id, count(reply_id) as total_replies from replies group by post_id) r on posts.post_id = r.post_id
inner join (Select post_id, count(like_id) as total_likes from likes group by posts_id) L on posts.post_id = L.post_id;
The caveat is you won’t get post that weren’t liked or replied upon since you are doing inner join. To get all posts irrespective of likes or replies you have to do left join.

Related

How to count make an SQL based on 3 tables

I have three tables and Im trying to count the number of likes per user on all his/her post.
USER TABLE
id name
1 John
2 Joe
POSTS TABLE
id user_id post_title
1 1 Some Title
2 1 Another Title
3 2 Yeah Title
LIKES TABLE
id post_id
1 1
2 1
3 1
4 2
5 3
My expected output is
ID LIKES
1 4
2 1
Im kinda stuck with the code below. I don't know how to add and count the likes table.
SELECT *
FROM user
INNER JOIN posts
ON user.id = posts.user_id;
You need to extend the join to the LIKES table and then use GROUP BY to group by the user ID and COUNT() all of the records for that user...
SELECT user.id, COUNT(likes.id)
FROM user
INNER JOIN posts ON user.id = posts.user_id
INNER JOIN likes ON posts.id = likes.post_id
GROUP BY user.id
If you want to list people who don't have posts or likes, then you should use outer joins (so change INNER JOIN to LEFT JOIN) so that these users show up.
For your desired result, you don't need the user table. You can simply do:
SELECT p.user_id, COUNT(*)
FROM posts p JOIN
likes l
ON l.post_id = p.id
GROUP BY p.user_id;
The only information you are taking from users is the id, which is already in posts. This assumes that all the user_id values in posts are valid, but that seems like a very reasonable assumption.

MySQL Frequency of frequency report

Given many users have many posts
I have a table of posts that has a foreign key user_id
I want to generate a report that shows the frequency of users against frequency of posts
e.g.
3 users wrote 2 posts each
2 users wrote 1 post each
1 user wrote 4 posts
Number of users | Number of posts
--------------- | ------------------
1 | 4
2 | 1
3 | 2
My attempt:
SELECT inner_table.frequency_posts,
Count(*) AS frequency_users
FROM posts
INNER JOIN (SELECT user_id,
Count(*) AS frequency_posts
FROM posts
GROUP BY user_id) AS inner_table
ON posts.user_id = inner_table.user_id
GROUP BY inner_table.frequency_posts
I think frequency_posts is working but counting frequency_users isn't giving the right values - when I look at the inner select on it's own and manually add up the posts I don't get the same values
You have to use Group by twice:
SELECT
COUNT(*) AS NumberOfUsers,
foo.NumberOfPosts
FROM
(SELECT
p.UserId AS UserId,
COUNT(*) AS NumberOfPosts
FROM
posts AS p
GROUP BY UserId) as foo
GROUP BY foo.NumberOfPosts

Order by joined table data

Here is my tables:
topics
topic_id name
1 "Help!"
2 "Hey!"
3 "What?"
posts
post_id topic date content
1 2 2016-05-01 "Hey there!"
2 1 2016-05-04 "How to use WIFI?"
3 1 2016-05-05 "I dont know"
4 1 2016-05-02 "What is WIFI?"
5 3 2016-05-06 "What what?"
6 2 2016-05-02 "Hello"
I have this code
SELECT * from topics
LEFT JOIN posts
ON posts.topic = topics.topic_id
I want to join the posts with last (most recent) record only, and sort the records from topics by posts.date,
but I don't how to.
Expected result:
topic_id post_id date ...
3 5 2016-05-06 ...
1 3 2016-05-05 ...
2 6 2016-05-02 ...
Please try this Query
SELECT * from topics LEFT JOIN posts ON posts.topic = topics.topic_id
Left join (Select posts.topics, Max(posts.date) as Date From posts Group by posts.topics) as postgroup
on posts.date = postgroup.date and posts.topic = postgroup.topics;
Let me assume that post_id defines the last record. You can do this in various ways. Here is a method using WHERE and a correlated subquery:
SELECT *
FROM topics t LEFT JOIN
posts p
ON p.topic = t.topic_id
WHERE p.post_id = (SELECT MAX(p2.post_id) FROM posts p2 WHERE p2.topic = p.topic)
ORDER BY p.post_date DESC;

Joining two tables based upon the highest COUNT(*) from table2?

Okay, so I have two tables that I need to link together with a JOIN query. There is a table called likes and a table called users. The users table looks something like this
id name
----- ------
1 Mark
2 Mike
3 Paul
4 Dave
5 Chris
6 John
The likes table looks like this.
user_one user_two match_id
----- ------ --------
1 2 abc
2 1
1 3 acc
3 1 abb
1 5 aee
5 1
The expected result should be
id name
----- ------
1 Mark
The two tables should only be linked on the rows in the likes table where the users_one column is set to the value that is most commonly found in that column. In this case, the user with the id of 1 is in the likes table with the user_one column 3 times where the match_id isn't empty.
I've thought it out to be written something like this
SELECT users.*, likes.COUNT(*) AS count
FROM users
JOIN likes
ON users.id = likes.user_one
WHERE likes.match_id != ''
But, I know this isn't correct. Is there a way to link two tables with a JOIN only on the most common rows in one of the tables?
Would Grouping work for what you need... ?
SELECT users.id, users.name, count(*) AS count
FROM users
JOIN likes
ON users.id = likes.user_one
WHERE likes.match_id != ''
group by users.id, users.name
should give you something like
1 Mark 3
Should be something like this, if I understood the question
select top 1 user_one, name
from likes
inner join users ON users.id = likes.user_one
where match_id != ''
group by user_one
order by count(*) Desc
Are you looking for something like this?
select u.id, u.name, count(*)
from users u
inner join likes l
on l.id = l.user_one and l.match_id != ''
group by u.id, u.name
order by count(*) desc
limit 1
The limit 1, combined with sorting by the # of likes in descending order will result in getting one user - the one with the most matched likes.
Try:
select *
from users
where id in (
select id
from likes
group by id
order by count(*) desc, id
limit 1
)
The subquery returns the id of the row with the most appearances in the likes table (group by id and order by count(*) desc). I've added id to the order by to give predictable results in case there are multiple with the same number of appearances. This is used to join to the users table to give the resultset required.

MYSQL GROUP_CONCAT multiple IDs and names (Multiple comment rows on multiple posts rows)

I have 3 MYSQL tables:
Posts
post_id post_name post_date
1 Hello 2013-04-23
2 Goodbye 2013-04-24
Users
user_id user_name
1 Danny
2 Max
Comments
comment_id user_id post_id comment_text comment_date
1 1 1 Really good 2013-04-23
2 2 2 Really bad 2013-04-24
3 2 2 Just joking 2013-04-24
My goal is to display multiple post rows, with multiple comments ( which have been con-catted with the user_id, user_name & comment_text & separated with delimiters ).
Something like this:
Result
Post id Post name Comments
1 Hello 1,Danny,Really good
1 Goodbye 2,Max,Really bad|2,Max,Just joking
I've been scratching my head an searching for examples of this for hours. Any help with the MYSQL query would be much appreciated! Thanks.
You can use CONCAT within GROUP_CONCAT
Something like this:-
SELECT a.post_id, a.post_name, GROUP_CONCAT(CONCAT_WS(",", c.user_id, c.user_name, b.comment_text) SEPARATOR "|")
FROM Posts a
INNER JOIN Comments b ON a.post_id = b.post_id
INNER JOIN Users c ON b.user_id = c.user_id
GROUP BY a.post_id, a.post_name