Selecting the most recent row from another table - mysql

I'm building a basic forum, and i want to grab the list of threads created but also grab the most recent post from the post table so i can display last reply and time of last reply
How ever my attempt is causing duplicate results, i tried a LEFT JOIN and a INNER JOIN with the same outcome. I was hoping someone knew the solution.
This is my attempt:
SELECT t1.username as thread_starter,
t2.username as last_reply_username,
t1.thread_time as thread_start,
t2.post_time as last_reply_time,
t1.title,
t1.sticky
FROM threads t1
INNER JOIN posts t2
ON t1.id = t2.threadid
ORDER BY t1.sticky DESC, t2.post_time DESC
Does any one know how i can solve the problem so it will only get the last and most recent post from the post table related to each thread without returning duplicate threads?

The idea behind the query below is that it finds the most recent post (post_time) from the post table for each threadid inside the subquery. Then it joins back on the original table post and threads table.
SELECT a.username AS Thread_Starter,
c.username AS Last_reply_username,
a.thread_time AS Thread_Start,
c.post_time AS Last_Reply_Time,
a.Title,
a.Sticky
FROM threads a
INNER JOIN
(
SELECT threadID, MAX(Post_Time) lastPost
FROM post
GROUP BY ThreadID
) b ON a.threadID = b.threadID
INNER JOIN post c
ON b.threadID = c.ThreadID AND
b.lastPost = c.post_time
ORDER BY a.sticky DESC, c.post_time DESC

Related

SQL check if thread timestamp is newer than reply timestamp in JOIN Statement

So I'm kinda new to SQL joins and was thinking on going full overkill probably.
What I want to do is join my four tables together.
What I want to accomplish is that I want all the information from category, and I want it to be matched to the replies with the newest timestamp and then I want to join the t.title which t.id matches r.thread_id
SELECT c.*, t.id, t.title, r.timestamp, u.id, u.username
FROM forum_category AS c
LEFT JOIN forum_threads AS t ON (c.id = t.category_id)
LEFT JOIN forum_replies AS r ON (t.id = r.thread_id
AND r.timestamp =
(
SELECT timestamp
FROM forum_replies
ORDER BY timestamp DESC LIMIT 1
))
LEFT JOIN users AS u ON (r.user_id = u.id)
GROUP BY c.id
As it is now this code seems to work, not having tested it alot.
However I need to expand it to check if t.timestamp is newer than latest r.timestamp and JOIN that one instead then. with the t.title, t.timestamp and t.user_id.
So if a thread is newer than the latest reply.
I know I could make the first post a reply and solve it that way. But I'm not doing that right now if it's possible to solve in the SQL statement.
SQL layout imgur here:
https://imgur.com/a/nCn2a
forum_category:
forum_threads:
forum_replies:
One helpful technique is to use Subqueries to break up the mental logic of what your query is trying to do. Basically, a subquery takes the place of a regular table in any query.
So, first up, we need to get the most recent time stamp in the replies for each thread:
select thread_id, max(timestamp) as LatestReply
from forum_replies
group by thread_id
Let's call this our MostRecentThreadSubquery. So, it would let us do something like:
select * from
forum_threads t
LEFT JOIN
(
select thread_id, max(timestamp) as LatestReply
from forum_replies
group by thread_id
) as MostRecentThreadSubquery
on t.thread_id = MostRecentThreadSubquery.thread_id
Make sense? We're no longer joining the forum_threads table against the forum_replies table - we've made a subquery to help us list the most recent reply for each thread id.
Now, we add the SQL CASE statement, to get something like:
select
thread_id,
CASE WHEN t.timestamp > MostRecentThreadSubquery.LatestReply
THEN t.timestamp
ELSE MostRecentThreadSubquery.LatestReply
END as MostRecentTimestamp
from -- ... the rest of that earlier SQL statement
Okay, so now we've got a query that, for every thread_id, has the most recent timestamp - whether that's from the forum_replies or from the forum_threads table.
... and you guessed it. We're going to make it another subquery. Let's call it our MostRecentPerThread
select *
from forum_category AS c
LEFT JOIN
(
-- ... that previous query ...
) as MostRecentPerThread
on c.thread_id = MostRecentPerThread.thread_id
Make sense? You're using subqueries as a way of logically breaking down your query into smaller components. You no longer have one gigantic query. You've got a small subquery that simply gets the timestamp of the most recent reply. You've got a small subquery that compares that first subquery to the threads table to get the most recent timestamp. And you've got a main query that uses the second subquery to merge it with the categories table.

How to sort groups in MySQL join operator?

In my sql I have this query
SELECT * FROM threads t
JOIN (
SELECT c.*
FROM comments c
WHERE c.thread_id = t.id
ORDER BY date_sent
ASC LIMIT 1
) d ON t.id = d.thread_id
ORDER By d.date_sent DESC
Basically I have two tables, threads and comments. Comments have a foreign key to the thread table. I want to get the earliest comment row for each thread row. Threads should have at least 1 comment. If it doesn't, then the thread row shouldn't be included.
In my query above, I do a select on thread, and then I join it with a custom query. I want to use t.id, where t is the select table outside the brackets. Inside the brackets I create a new result set thats comments are for the current thread. I do the sorting and limiting there.
Then afterwards, I sort it again, so its earliest on top. However when I run this, it gives an error #1054 - Unknown column 't.id' in 'where clause'.
Does anyone know whats wrong here?
Thanks
The unknown column t.id is due to the fact that the alias t is unknown inside the subquery, but indeed it isn't needed anyway since you join it in the ON clause.
Instead of a LIMIT 1, use a MIN(date_sent) aggregate grouped by thread_id in the subquery. Be careful also using SELECT * in a join query, if columns in both tables have the same names; better to list the columns explicitly.
SELECT
/* List the columns you explicitly need here rather than *
if there is any name overlap (like `id` for example) */
t.*,
c.*
FROM
threads t
/* join threads against the subquery returning only thread_id and earliest date_sent */
INNER JOIN (
SELECT thread_id, MIN(date_sent) AS firstdate
FROM comments
GROUP BY thread_id
) earliest ON t.id = earliest.thread_id
/* then join the subquery back against the full comments table to get the other columns
in that table. The join is done on both thread_id and the date_sent timestamp */
INNER JOIN comments c
ON earliest.thread_id = c.thread_id
AND earliest.firstdate = c.date_sent
ORDER BY c.date_sent DESC
Michael's answer is correct. This is another answer that follows more the form of your query. You can do what you want as a correlated subquery and then join in the additional information:
SELECT *
FROM (SELECT t.*,
(SELECT c.id
FROM comments c
WHERE c.thread_id = t.id
ORDER BY c.date_sent ASC
LIMIT 1
) as mostrecentcommentid
FROM threads t
) t JOIN
comments c
on t.mostrecentcommentid = c.id
ORDER By c.date_sent DESC;
It is possible that this has better performance, because it does not require aggregating all the data. However, for performance, you would want an index on comments(thread_id, date_set, id).

JOIN a table to the MAX records from another table

Let's say that I have a MySQL database for an online Forum with two tables :
topic : id, ...
message : id, id_topic, created, ...
One topic could have many (or no) messages, as you'd expect.
I would like to get a list of every topic record LEFT JOIN'ed to its most recently-created message record (or joined to nothing, if there are no messages). I need all fields of both records in the result.
What I've been doing is using a sub-query to make a table of all the most recently-created messages, then joining it to the topic table. However it really bogs down when the topic table has a few thousand rows and the message table has a few hundred thousand.
What is the correct way to solve this problem, with an eye toward performance?
Try this:
SELECT t.*, m.*
FROM topic t
LEFT JOIN
(SELECT id_topic, MAX(created) AS created
FROM message
GROUP BY id_topic
) T2 ON t.id = T2.id_topic
LEFT JOIN message m ON m.id_topic = T2.id_topic AND m.created = T2.created
Explanation:
First, we are joining the table topic with a temp table having maximum created for each id_topic. Then the table message is joined with the same temp table. And then the result are fetched from both tables.
This should do it. It should have good performance too
(provided your message table is properly indexed).
SELECT t.*, m3.*
FROM
topic t
LEFT JOIN
(
SELECT m1.id_topic, max(m1.id) id
FROM
message m1
GROUP BY m1.id_topic
) m2 ON t.id = m2.id_topic
LEFT JOIN message m3 ON m2.id = m3.id

MySQL Group By single result order by time

I am trying to fetch users from messages table which has send message to me but need only one result per sender which is max of creation column which is time of message creation
SELECT messages.from, account_images.profile, bio.user_full_name
FROM messages
INNER JOIN account_images ON account_images.uuid=messages.from
INNER JOIN bio ON bio.uuid=messages.from
WHERE messages.to='me'
GROUP BY messages.from
ORDER BY messages.creation DESC
A user whose message is created recently must be on top but it not coming on top using this code. I referred php mysql Group By to get latest record, not first record but not getting anything
Any help?
You could have written the current query as...
SELECT DISTINCT messages.from, account_images.profile, bio.user_full_name
FROM messages
INNER JOIN account_images ON account_images.uuid=messages.from
INNER JOIN bio ON bio.uuid=messages.from
WHERE messages.to='me'
Which would have been marginally more efficient - but as you will have noticed with your current query, it doesn't get sorted by the most recent message.
This is sorted as you request:
SELECT messages.from, account_images.profile, bio.user_full_name
FROM messages
INNER JOIN account_images ON account_images.uuid=messages.from
INNER JOIN bio ON bio.uuid=messages.from
WHERE messages.to='me'
GROUP BY messages.from
ORDER BY MAX(messages.creation) DESC;
It is possible to get the result you desire using DISTINCT instead of aggregation, but it's neither efficient nor elegant.
I found this mysql doc very helpful in cases like this: http://dev.mysql.com/doc/refman/5.0/en/example-maximum-row.html
SELECT *
FROM messages m
LEFT JOIN messages m2 ON m.creation > m2.creation
WHERE m2.creation IS NULL
AND m1.to='me'

sql nested inner join only returning 1 result

I'm building a Chatapplication that's a bit like the facebookchat. I have users,conversations and messages. All 3 have their own tables. For now I try to get all converstations containing a certain user and the latest message of the conversation.
I tried this query, but in a fact I only get 1 row back, but there are more rows matching
SELECT conversations.id as converid,
messages.from as messageauthor,
messages.message as message
FROM conversations INNER JOIN (SELECT * FROM messages
ORDER BY date DESC LIMIT 1) as messages
ON messages.conversationid=conversations.id
WHERE user1=3
OR user2=3
When I do i.e.
SELECT conversations.id as converid,
messages.from as messageauthor
FROM conversations INNER JOIN messages
ON messages.conversationid=conversations.id
WHERE user1=3
OR user2=3
I get all results, for sure, and when I check the converid's I get 3 unique Id's, so at least there are 3 converstations going on with userid 3. So the top query should also return 3. Now I don't understand why it only returns 1 row. Does the limit 1 from the nested query affect the whole query?
Looking forward for some pointers...
No. The limit 1 affects the subquery, so it is only returning one row. So, there is only one match.
What is the issue with this query (your second query, but formatted differently):
SELECT c.id as converid, m.from as messageauthor
FROM conversations c INNER JOIN
messages m
ON m.conversationid=c.id
WHERE user1=3 OR user2=3;
I see, you want the latest message. Try calculating it and joining back in:
SELECT c.id as converid, m.from as messageauthor
FROM conversations c INNER JOIN
messages m
ON m.conversationid=c.id join
(select m.conversationid, max(date) as maxdate
from messages m
group by m.conversationid
) mmax
on mmax.conversationid = m.conversationid and m.date = mmax.maxdate
WHERE user1=3 OR user2=3;