MySQL: Joining 4 tables with correct results - mysql

I am have four tables in my DB for a simple forum that I am coding.
Topics:
topic_ID | name | description
Threads:
thread_ID | topic_ID | name | description
Messages:
message_ID | thread_ID | title | message | date | user_ID
Users:
user_ID | name | email | username
I want to run one query to do the following:
Display the available topics, the number of threads associated with each topic, the number of messages associated with each topic, the date latest message posted and the user who posted it.
So one row of the result would say something like:
Topic: Admin
Threads: 4
Posts: 50
Newest message: 2016/05/18 by pixelled
I started with this (which worked):
SELECT topics.topic_id, topics.name, count(threads.topic_id) AS 'totals'
FROM topics
LEFT JOIN threads
ON topics.topic_id = threads.topic_id
GROUP BY threads.topic_id
I then added the messages table:
SELECT topics.topic_id, topics.name, count(threads.topic_id) AS 'totals', MAX(messages.date) AS 'Newest'
FROM topics
LEFT JOIN threads
ON topics.topic_id = threads.topic_id
LEFT JOIN messages
ON messages.thread_id = threads.thread_id
GROUP BY threads.topic_id
But the results of this query show the wrong values for the totals column.
Adding the users table works:
SELECT topics.topic_id, topics.name, count(threads.topic_id) AS 'totals', MAX(messages.date) AS 'Newest', users.username
FROM topics
LEFT JOIN threads
ON topics.topic_id = threads.topic_id
LEFT JOIN messages
ON messages.thread_id = threads.thread_id
LEFT JOIN users
ON users.user_ID = messages.user_ID
GROUP BY threads.topic_id
Please help me to complete this query so that the correct value shows in the totals column.
Here is the fiddle:
http://sqlfiddle.com/#!9/0f926

Try
count (distinct threads.thread_id)

This was the query that gave the correct results:
SELECT topics.topic_id, topics.name, topics.description, count(threads.topic_id) AS 'totals', MAX(m.date) AS 'Newest', users.username
FROM topics
LEFT JOIN threads
ON topics.topic_id = threads.topic_id
LEFT JOIN
(
SELECT thread_id, date, user_id
FROM messages
GROUP BY thread_id
) m
ON m.thread_id = threads.thread_id
LEFT JOIN users
ON users.id = m.user_id
GROUP BY threads.topic_id
It was duplicating the threads table because it was joined twice.

Related

Count comments and get average rating from mysql

I just can't figure out how to get average rating and count comments from my mysql database.
I have 3 tables (activity, rating, comments) activity contains the main data the "activities", rating holds the ratings and comments - of course, the ratings.
activity_table
id | title |short_desc | long_desc | address | lat | long |last_updated
rating_table
id | activityid | userid | rating
comment_table
id | activityid | userid | rating
I'm now trying to the data from activity plus the comment_counts and average_rating in one query.
SELECT activity.*, AVG(rating.rating) as average_rating, count(comments.activityid) as total_comments
FROM activity LEFT JOIN
rating
ON activity.aid = rating.activityid LEFT JOIN
comments
ON activity.aid = comments.activityid
GROUP BY activity.aid
...doesn't do the job. It gives me the right average_rating, but the wrong amount of comments.
Any ideas?
Thanks a lot!
You are aggregating along two different dimensions. The Cartesian product generated by the joins affects the aggregation.
So, you should aggregate before the joins:
SELECT a.*, r.average_rating, COALESCE(c.total_comments, 0) as total_comments
FROM activity a LEFT JOIN
(SELECT r.activityid, AVG(r.rating) as average_rating
FROM rating r
GROUP BY r.activityid
) r
ON a.aid = r.activityid LEFT JOIN
(SELECT c.activityid, COUNT(*) as total_comments
FROM comments c
GROUP BY c.activityid
) c
ON a.aid = c.activityid;
Notice that the outer GROUP BY is no longer needed.

MySQL joining table's maximum row contents

For a simple forum I would like to list the threads in order from most recent post. That I have completed with no issue with the following query:
SELECT t.thread_id, t.title, MAX(m.post_id) as latest_reply
FROM forum_thread as t
LEFT JOIN forum_post as n ON(latest_reply = m.thread_id)
WHERE t.forum_id=:forum_id AND t.sticky=0
GROUP BY t.thread_id
ORDER BY latest_reply DESC
LIMIT :limit_bottom, :limit_top
This works fine, until I want more details from the maximum post row. If I select more info from the post table, the results are random
I would like to also know, the post title, and then the username who posted.
The thread table [forum_thread] looks like the following:
thread_id | forum_id | title | sticky | post_count | view_count | closed
The post table [forum_post] looks like the following:
post_id | user_id | thread_id | timestamp | title | post_body_cache
And the user table [user] looks like the following:
id | username
I need to join the maximum post row to get the title, and than join the user table to get the username. What is the most efficient way of doing this? Everything I have tried has returned a fatal error.
Desired results would be:
[forum_thread] thread_id | [forum_thread] title | [forum_post] MAX post_id | [forum_post] title | [user] username
To get the entire row from the outer table, you have to move the Max to a subquery and use it in the joining criteria:
SELECT t.thread_id, t.title, p.author, p.post_date, p.whatever
FROM forum_thread as t
LEFT JOIN forum_post as p
ON p.thread_id = m.thread_id
and p.post_date =(
select Max( p.post_date )
from forum_post
where thread_id = p.thread_id )
WHERE t.forum_id = :forum_id
AND t.sticky= 0

MySQL query comment number of articles together with article details

I have three tables, Users, Articles and Comments, as below:
Users:
id | username
Articles:
id | user_id | title | content | time
Comments:
id | article_id | content | time
user_id is the article writer's user id in Users; article_id is the commenting article's id in Articles.
Now I want to list articles, showing their writer's username, title, content, time, and number of comments. The username is from Users, number of comments should be from Comments by counting something, and the rest fields are from Articles.
How to write the query in a single statement?
I tried
SELECT a.*, count(c.id) AS NUM_COMMENTS, u.username
FROM Commetns c
JOIN Users u
INNER JOIN Articles a
WHERE a.id = c.article_id AND u.id=a.user_id
GROUP BY a.id
But this only returns articles with comments. I want the articles without comments also listed.
You need left join to get the articles without comments
select
a.*,
count(c.id) AS NUM_COMMENTS,
u.username
from Articles a
JOIN Users u on u.id=a.user_id
left join Commetns c on c.article_id = a.id
GROUP BY a.id

Count not giving right results with three joins

I have three tables (MySQL)
forum: each line in this table is a comment in the forum related to the match by static_id and related to the author by user_id
|match_static_id| date | time | comments | user_id |
matches: this table contains matches with all its information
| static_id | localteam_name | visitorteam_name | date | time |.......
iddaa : this table contains a code for each match (some matches do not have codes here)
|match_static_id| iddaa_code |
I make a query like following:
SELECT forum.match_static_id, forum.date, forum.time,
count(forum.comments) 'comments_no', matches.*, users.username, iddaa.iddaa_code
FROM forum
INNER JOIN matches ON forum.match_static_id = matches.static_id
INNER JOIN users on forum.user_id = users.id
LEFT JOIN iddaa on forum.match_static_id = iddaa.match_static_id
GROUP BY forum.match_static_id
ORDER BY forum.date DESC, forum.time DESC
the query work as I want (I get the match information, iddaa code for the match if there is one, and the author of the comment(last comment) ).
The problem is in the "count function" I should get the number of the comments related to the same match bur the query returned (double of each value)
for example if I have 5 comments for a match it returns 10
I want to know if all parts of my query is right and any help will be good?
Maybe it can be wrapped in a sub query? Its hard when i dont have the table def + data.
SELECT Sub.*, COUNT(1) 'comments_no'
FROM
(
SELECT forum.match_static_id, forum.date, forum.time,
matches.*, users.username, iddaa.iddaa_code
FROM forum
INNER JOIN matches ON forum.match_static_id = matches.static_id
INNER JOIN users on forum.user_id = users.id
GROUP BY forum.match_static_id
) Sub
LEFT JOIN iddaa on Sub.match_static_id = iddaa.match_static_id
ORDER BY forum.date DESC, forum.time DESC

Group messages by latest response in conversation threading

I need a simple internal messaging system between users.
My tables:
+--------------+ +---------------------+
| messages | | users |
+----+---------+ +---------------------+
| id | message | | id | username | ...
+----+---------+ +---------------------+
+------------------------------------------------------------------------------+
| users_messages |
+------------------------------------------------------------------------------+
| id | from_usr_id | to_usr_id | msg_id | thread_id | read | sent_at | read_at |
+------------------------------------------------------------------------------+
INT 'thread_id' represents the conversation thread, its used to group messages.
BOOLEAN 'read' represents if the user opened/viewed the message or not.
I want to group messages by 'thread_id', sorted by 'sent_at' so I can show the user his latest messages by thread. I want also to count the messages in each thread.
I want to get something like this for a specific user id:
+----------------------------------------------------------------------------
| last_messages_by_conversation
+----------------------------------------------------------------------------
| message | from_username | sent_at | count_thread_msgs | count_unread_msg |
+----------------------------------------------------------------------------
TEXT 'message' is the latest message in the specific 'thread_id'
VARCHAR 'from_username' and DATETIME 'sent_at' are related to the latest message.
INT 'count_thread_msgs' and INT 'count_unread_msg' are related to the thread, representing the total number of messages and the number of unread messages in the thread.
Each row represents a thread/conversation (group by 'thread_id'), showing the last message (sorted by 'sent_at') for that specific thread.
You are looking for the groupwise maximum, which can be found by first grouping the users_messages table by thread_id and selecting MAX(sent_at), then joining the result back onto the users_messages table to find the other fields of that maximum record.
I find that NATURAL JOIN is a very handy shortcut here:
SELECT messages.message,
users.username AS from_username,
t.sent_at,
t.count_thread_msgs,
t.count_unread_msg
FROM users_messages NATURAL JOIN (
SELECT thread_id,
to_usr_id,
MAX(sent_at) AS sent_at,
COUNT(*) AS count_thread_msgs,
SUM(NOT read) AS count_unread_msg
FROM users_messages
WHERE to_usr_id = ?
GROUP BY thread_id
) t JOIN messages ON messages.id = users_messages.msg_id
JOIN users ON users.id = users_messages.from_usr_id
SELECT
users.id,
users.username,
user_messages.thread_id,
user_messages.unread ,
messages.message
FROM users
LEFT JOIN (SELECT
from_usr_id ,
msg_id,
count(thread_id)) as thread_id,
count(read_at) as unread
FROM user_messages)as user_messages on user_messages.from_usr_id = users.id
LEFT JOIN messages on messages.id = user_messages.msg_id
You can try this solution:
SELECT c.message,
d.username AS from_username,
b.sent_at,
a.count_thread_msgs,
a.count_unread_msg
FROM (
SELECT MAX(id) AS maxid,
COUNT(*) AS count_thread_msgs,
COUNT(CASE WHEN `read` = 0 AND <uid> = to_usr_id THEN 1 END) AS count_unread_msg
FROM users_messages
WHERE <uid> IN (from_usr_id, to_usr_id)
GROUP BY thread_id
) a
JOIN users_messages b ON a.maxid = b.id
JOIN messages c ON b.msg_id = c.id
JOIN users d ON b.from_usr_id = d.id
ORDER BY b.sent_at DESC
This gets the latest message in each thread that the user <uid> started or is a part of.
The latest message is based on the highest id of each thread_id.
This solution makes the following assumptions:
The id in users_messages is a unique auto-incrementing int for each new row.
Each thread contains correspondence between never more than two users.
If the thread can contain more than two users, then the query will need to be slightly adjusted so as to derive an accurate count aggregation.
Try this and let me know, change $$ for your user ID..
select u.username,msg.message,m.sent_at,
(select count(*) from user_message where read=0 and to_usr_id=$$) as count_thread_msgs,
(select count(*) from user_message where to_usr_id= $$) as count_unread_msg
from users as u join user_messages as m
on u.id=m.id where u.id=$$
join messages as msg on msg.id=m.id
group by u.id;`
Try this query -
SELECT
m.message,
u.username from_username,
um1.sent_at,
um2.count_thread_msgs,
um2.count_unread_msg
FROM users_messages um1
JOIN (
SELECT
thread_id,
MAX(sent_at) sent_at,
COUNT(*) count_thread_msgs,
COUNT(IF(`read` = 1, `read`, NULL)) count_unread_msg
FROM users_messages GROUP BY thread_id) um2
ON um1.thread_id = um2.thread_id AND um1.sent_at = um2.sent_at
JOIN messages m
ON m.id = um1.msg_id
JOIN users u
ON u.id = um1.from_usr_id
-- WHERE u.id = 100 -- specify user id here
Answers on your questions:
About last datetime: I have changed query a little, just try new one.
About specific users: Add WHERE condition to filter users - ...WHERE u.id = 100.
About many records: because you join another tables (messages and users), and there can be more then one record with the same thread_id. To avoid this you should group result set by thread_id field and use aggregate function to get single result, e.g. using GROUP_CONCAT function.