MySQL joining table's maximum row contents - mysql

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

Related

Order by last row of SELECT subquery

I have 2 tables: "posts" - for posts that a user post, and all the information it includes. and "messages" - which includes messages for each post.
The tables look like this:
posts:
--------------------------------------------------
| post_id | post_creator_id | post_information |
--------------------------------------------------
messages:
----------------------------------------------------------
| message_id | post_id | user_id | message_text |
----------------------------------------------------------
I want to display a user (user_id) a list of all chats he is engaged in, and order it by the last message sent in any chat.
So there are a few things to be done: first I need to group by the post_id in messages table (so there aren't duplicates) and order by message_id DESC, I tried subquery and joins but neither worked. Here is an example of my subquery attempt:
SELECT posts.post_information FROM posts WHERE posts.post_id in (SELECT
messages.post_id FROM messages ORDER BY message_id DESC)
Can't figure out how make the query display only distinct rows (without duplication of post_id) And whether join is a better option?
Simple change in your query, we can use INNER JOIN with messages table and ORDER BY with message_id instead of using suqbyery (It seems that you want to order output data by message_id in descending order.
SELECT posts.post_information
FROM posts
INNER JOIN (SELECT post_id, max(message_id) mid
FROM messages
GROUP BY post_id) mes ON mes.post_id = posts.post_id
ORDER BY mes.mid DESC
Assuming you have multiple messages per post, you need to use distinct not to include duplicates. Than just join tables and add order by
SELECT distinct posts.post_information
FROM posts, messages where messages.post_id = posts.post_id
ORDER BY messages.message_id DESC

mysql search with two joins and a count

I am currently trying to write a general query which returns the content of 1 table and another joined table plus the count of resulting rows from a third table.
Now my description might seem abstract so I'll try to visualize it
Tables:
posts
| ID | title | description | creator_id |
1 Title1 Descr1 1
2 Title2 Descr2 1
users
| ID | name | avatar |
1 User1 PATH
interactions
| ID | type | target_id | identifier |
1 view 1 IP
2 view 1 IP
Now what I am looking for is an output like this:
| ID | title | description | name | avatar | view_count |
1 Title1 Descr1 User1 PATH 2
2 Title2 Descr2 User1 PATH 0
My current query looks like following:
SELECT
posts.id, posts.title, posts.description,
users.name, users.avatar,
COUNT(interactions.id) AS view_count
FROM
posts
LEFT JOIN
users
ON
posts.creator_id = users.id
LEFT JOIN
interactions
ON
posts.id = interactions.target_id
But only prints out the posts result which has an interaction like this:
| ID | title | description | name | avatar | view_count |
1 Title1 Descr1 User1 PATH 2
How do I need to alter the query in order to also get the other rows which happen to not have any interactions yet?
Thank you for your help!
You can simply subquery third table to count entries:
SELECT
posts.id, posts.title, posts.description,
users.name, users.avatar,
(SELECT COUNT(*) FROM interactions i WHERE i.target_id = posts.id) AS view_count
FROM
posts
LEFT JOIN
users
ON
posts.creator_id = users.id
This is also better for performance (no groups, no unoptimized joins)
Try this:
SELECT P.ID
, P.title
, P.description
, U.name
, U.avatar
, IFNULL(COUNT(I.ID), 0) AS view_count
FROM posts P
LEFT JOIN users U ON U.ID = P.creator_id
LEFT JOIN interactions I ON I.target_id = P.ID
GROUP BY P.ID
It seems like you missed the GROUP BY clause. Without this, when you use an aggregate function like COUNT, the documentation says:
there is a single group and it is indeterminate
which name value to choose for the group
That's why your query only returned 1 row.
Try this;)
select posts.id, posts.title, posts.description, users.name, users.avatar, coalesce(t3.view_count, 0) as view_count
from posts
left join users on posts.creator_id = users.id
left join (
select target_id, count(1) as view_count from interactions group by target_id
) t3 on posts.id = t3.target_id
SQLFiddle HERE

mySQL - Select 2 latest comments for each posts

I trying to limit for only 2 comments for each posts,i select in the post table and i want to get 2 comments for each
Schema:
Posts table
------------------------------------
id | content | date |
25 | hello | 20/10/2013 |
Comments TABLE
------------------------------------------------
id | content | post | date |
1 | hello | 25 | 20/10/2013 |
Could you help me friends, i'm so confused !
Thanks before, Any help will be appreciate.
This query returns last 2 comments for each post:
SQLFiddle demo
select p.content post_content,
c.content comment_content
from posts p
left join comments c on
(p.id=c.post)
and
c.id>
(select id from comments
where post=p.id
order by id DESC LIMIT 2,1)
Syntax may not be perfect, did not have time to create fiddle. But this has subquery which should get the latest 2 comments related to the post and join that to the post itself. Have to consider that fact that there may be no comments at all hence the test for Is Null from the Left Join.
Select *
From Posts p
Left Outer Join Comments c
On c.post = p.id
Where
( c.id Is Null
Or c.id In
(
Select c2.id
From Comments c2
Where c2.post = p.id
Order by c2.id Desc
Limit 2
)
)
MySQL supports the LIMIT keyword, which allows you to control how many rows are returned; ideal when displaying data over many pages. You can use LIMIT in your sql query like this
In your case
select * from posts p join comments c on p.id=c.post and
c.id> (select id from comments where post=p.id order by id DESC LIMIT 2,1)

Duplicate content in LEFT JOIN query + get count on joined table

I am trying to join two tables with similar ids, then get a sum of two fields as well. let me explain:
test table: id | post | desc | Date
likes_dislikes table: id | song_id | user_ip | like | dislike
on test 'test table', the 'id' matches that of the likes_dislikes 'song_id', so I tried LEFT JOIN since not every post will have an id in the likes_dislikes table, but I got duplicate results .
SELECT *
FROM
test
LEFT JOIN likes_dislikes ON test.song_id = likes_dislikes.page_id
GROUP BY test.song_id
ORDER BY test.id DESC LIMIT $start, $limit
how can I prevent the duplicate content, and also, get the TOTAL likes/dislikes associated with each post as I run through a while loop?
I Assume you are looking for something like this:
SELECT
T.`id`,
T.`post`,
T.`desc`,
T.`Date`,
COUNT(L.`like`) as `LikeCount`,
COUNT(L.`dislike`) as `DislikeCount`
FROM `test` T
LEFT JOIN `likes_dislikes` L
ON T.`Id` = L.`song_id`
GROUP BY T.`Id`, T.`post`, T.`desc`, T.`Date`
ORDER BY T.`id` 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.