I made a post 6 days ago but no one seemed to anwser correctly and it was eventually abandoned so here I post it again (sorry for DPing but this is important to me)
I have 2 tables - people (query is based on people.id so no need to ss the entire table) and messages (http://prntscr.com/94iq2e)
I have a query which is grouping messages with people and that is working fine but each person is grouped with the first message he sent and I need to make it so that it groups people with the LAST message they sent
Here is the query which is grouping people with the FIRST message
SELECT people.id,
people.avatar,
people.firstname,
people.lastname,
LEFT(messages.message, 90) AS message,
messages.seen,
messages.date
FROM people
INNER JOIN messages
ON people.id = messages.sender_id
WHERE reciever_id = '". $user_data['id'] ."'
GROUP BY sender_id
ORDER BY date DESC limit 11
Link to the previous topic -> Selecting last record from INNER JOIN and grouping
You could use a subquery that returns the maximum date:
SELECT sender_id, MAX(date) AS max_date
FROM messages
GROUP BY sender_id
and join people table with this subquery, and then join to the messages table to get the message with the maximum date:
SELECT
people.id,
people.avatar,
people.firstname,
people.lastname,
LEFT(messages.message, 90) AS message,
messages.seen,
messages.date
FROM
people INNER JOIN (
SELECT sender_id, MAX(date) AS max_date
FROM messages
GROUP BY sender_id
) lm ON people.id = lm.sender_id
INNER JOIN messages
ON people.id = messages.sender_id AND
lm.max_date=messages.date
WHERE
reciever_id = ...
ORDER BY
...
I would recommend using a sub query to pull out the latest messages. The below link may of help to you, it details a few possible solutions.
SQL join: selecting the last records in a one-to-many relationship
Cheers,
Bob
I dont know mysql but in TSQL it would be like this to get last message:
ISNULL(SELECT TOP 1 messages.message FROM messages WHERE people.id = messages.sender_id ORDER BY messages.id DESC,'')
Whole code would look like this:
SELECT people.id,
people.avatar,
people.firstname,
people.lastname,
ISNULL(SELECT TOP 1 messages.message FROM messages WHERE people.id = messages.sender_id ORDER BY messages.id DESC,'') AS message,
messages.seen,
messages.date
FROM people
INNER JOIN messages
ON people.id = messages.sender_id
WHERE reciever_id = '". $user_data['id'] ."'
GROUP BY sender_id
ORDER BY date DESC limit 11
Related
I came across this very simple case where I need to select a list of conversations from Conversations table along with latest message from Messages table - which has non-uniqe dateCreated field.
After long research I came up with this query:
SELECT
Conversations.id,
dateCreated,
`name`,
lastMessageId,
lastMessageDate,
lastMessagePayload
FROM Conversations
LEFT JOIN (
SELECT
id AS lastMessageId,
m1.conversationId,
payload AS lastMessagePayload,
m1.dateCreated AS lastMessageDate,
FROM Messages AS m1
INNER JOIN (
SELECT conversationId, MAX(dateCreated) AS mdate FROM Messages GROUP BY conversationId
) AS m2
ON m1.conversationId = m2.conversationId AND m1.dateCreated = m2.mdate
) AS msg2
ON msg2.conversationId = Conversations.id
ORDER BY dateCreated DESC
Query works well but if two latest messages in same conversation have exact same dateCreated field this query would then output two conversations with same id but different lastMessage... row of fields.
I just couldn't find a way to get around this problem as main problem is when you do GROUP BY a field and MAX on another non-uniqe field then you can't get out always only one row out.
Any idea how to get list of unique conversations with latest message (any message of the two if they have the same date)?
Use row_number()!
select c.*, m.* -- or whatever columns you want
from conversations c left join
(select m.*,
row_number() over (partition by m.conversationid order by m.dateCreated desc, m.id desc) as seqnum
from messages m
) m
on m.conversation_id = c.id and
m.seqnum = 1;
MySQL 5.x version...
Use a correlated sub-query to get the latest message id (for a given conversation), using ORDER BY and LIMIT 1
SELECT
Conversations.Conversations.id,
Conversations.dateCreated,
Conversations.`name`,
Messages.id AS lastMessageId,
Messages.payload AS lastMessagePayload,
Messages.dateCreated AS lastMessageDate,
FROM
Conversations
LEFT JOIN
Messages
ON Messages.id = (
SELECT lookup.id
FROM Messages AS lookup
WHERE lookup.conversationId = Conversations.id
ORDER BY lookup.dateCreated DESC
LIMIT 1
)
ORDER BY
Conversations.dateCreated DESC
In the event of two messages having the same date, the message you get is non-deterministic / arbitrary.
You could, if you wanted, therefore change it to get the highest id from the most recent date...
ORDER BY lookup.dateCreated DESC, lookup.id DESC
LIMIT 1
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;
I'm trying to get latest created datetime for unique user_id. I've got below query but it does not seem to be working....It does not get the latest created time.
SELECT * FROM `chats`
WHERE receiver_id = 1
GROUP BY user_id
ORDER BY created DESC
Is there a reason why?
UPDATE:
Actually I found my answer myself. Please look below. I had to use INNER JOIN for nested searched and filtered result table then find using where clause of that result then left join on that table to get the data I needed!
SELECT c.*, users.username
FROM chats c
INNER JOIN(
SELECT MAX(created) AS Date, user_id, receiver_id, chat, type, id
FROM chats
WHERE receiver_id = 1
GROUP BY user_id ) cc ON cc.Date = c.created AND cc.user_id = c.user_id
LEFT JOIN users ON users.id = c.user_id
WHERE c.receiver_id = 1
You should use a MAX aggregate function -
SELECT user_id, MAX(created) latest_datetime FROM chats
GROUP BY user_id;
Try this query, it will show all users and latest created datetime.
I have the following tables:
users:
user_id
user_name
message:
message_id
thread_id
to_id
from_id
title
message_text
message_date
status
The desired result of the query I run is to list the title of the most recent message from the most recent threads a long with the thread_id and username and to sort the result by date in descending order. I'm only going to be listing about 10 to 20 results at a time most likely.
The SQL query I came up with seems to be doing this so far, but I feel like I have over complicated it and that there may be a more optimal way to write my query.
SELECT personal_messages.message_id,
personal_messages.thread_id,
personal_messages.body,
users.username
FROM users, personal_messages
WHERE message_id IN
(SELECT MAX(message_id) from personal_messages GROUP BY thread_id)
AND users.id IN
(SELECT users.id FROM users WHERE users.id = personal_messages.from_id)
ORDER BY personal_messages.message_date DESC
Also, if anyone knows a way to get the count of all the messages with the same thread_id, that would be awesome!
Any tips would be greatly appreciated!
One of the subselects could be unnecessary
SELECT personal_messages.message_id, personal_messages.thread_id, personal_messages.body, users.username
FROM users INNER JOIN personal_messages ON (users.id = personal_messages.from_id)
WHERE message_id IN (SELECT MAX(message_id) from personal_messages GROUP BY thread_id)
ORDER BY personal_messages.message_date DESC
Edit: Also, if anyone knows a way to get the count of all the messages with the same thread_id, that would be awesome!
SELECT MAX(message_id), COUNT(message_id) FROM personal_messages GROUP BY thread_id
I don't think there are too many subqueries in your query but it can be written more optimally. I think the below query should work equally well:
SELECT `PM`.`message_id`, `PM`.`thread_id`, `PM`.`body`, `U`.`username`
FROM `users` `U`
INNER JOIN `personal_messages` `PM` ON `PM`.`from_id` = `U`.`user_id`
ORDER BY `PM`.`message_date` DESC;
The issues that I've found with your query are:
The first sub-query - SELECT MAX(message_id) from personal_messages GROUP BY thread_id - will always return a single resultset, so using an IN doesn't make sense
For the second sub-query - SELECT users.id FROM users WHERE users.id = personal_messages.from_id - you may use an INNER JOIN instead, like in my example
Hope the above helps.
Also, I'm not sure on what info are you actually trying to retrieve. But the above query should give you the details for the most recent message, along with its thread id and username.
EDITED query to select one latest message per thread:
SELECT *
FROM (
SELECT `PM`.`message_id`, `PM`.`thread_id`, `PM`.`body`, `U`.`username`
FROM `users` `U`
INNER JOIN `personal_messages` `PM` ON `PM`.`from_id` = `U`.`user_id`
ORDER BY `PM`.`message_date` DESC;
) `TT`
GROUP BY `TT`.`thread_id`;
SELECT tbl.message_id, personal_messages.thread_id
, personal_messages.body, users.username
FROM users u
JOIN personal_messages tbl on tbl. from_id = u.UsersId
WHERE tbl.message_id IN (SELECT MAX(message_id) from personal_messages
GROUP BY thread_id)
ORDER BY personal_messages.message_date DESC
What you have already is pretty good. If you want to do it all with one query, you could get the COUNT(message_id) GROUP BY thread_id as part of the same subselect that gets the MAX(message_id) GROUP BY thread_id:
SELECT personal_messages.message_id,
personal_messages.thread_id,
personal_messages.body,
users.username,
thread.countmessages
FROM personal_messages
JOIN users ON users.id=personal_messages.from_id
JOIN
(SELECT COUNT(message_id) countmessages,
MAX(message_id) maxmessage_id
FROM personal_messages
GROUP BY thread_id) AS thread
ON thread.maxmessage_id=personal_messages.message_id
ORDER BY personal_messages.message_date DESC
I have the following tables for a messaging system and I was wondering how I would go about querying the DB for how many conversations have new messages.
My tables are as follows
Conversation
------------
id
subject
Messages
--------
id
conversation_id
user_id (sender)
message
timestamp (time sent)
Participants
------------
conversation_id
user_id
last_read (time stamp of last view user viewed conversation)
I'm trying to do the following query but it returns no results:
SELECT COUNT(m.conversation_id) AS count
FROM (messages_message m)
INNER JOIN messages_participants p ON p.conversation_id = m.conversation_id
WHERE `m`.`timestamp` > 'p.last_read'
AND `p`.`user_id` = '5'
GROUP BY m.conversation_id
LIMIT 1
Also, I probably will have to run this on every page load - any tips of making it as fast as possible?
Cheers
EDIT
I've got another somewhat related question if anybody would be so kind as to help out.
I'm trying to retrieve the subject, last message in conversation, timestamp of last convo and number of new messages. I believe I have a working query but it looks a bit badly put together. What sort of improvements can I do to this?
SELECT SQL_CALC_FOUND_ROWS c.*, last_msg.*, new_msgs.count as new_msgs_count
FROM ( messages_conversation c )
INNER JOIN messages_participants p ON p.user_id = '5'
INNER JOIN ( SELECT m.*
FROM (messages_message m)
ORDER BY m.timestamp DESC
LIMIT 1) last_msg
ON c.id = last_msg.conversation_id
LEFT JOIN ( SELECT COUNT(m.id) AS count, m.conversation_id, m.timestamp
FROM (messages_message m) ) new_msgs
ON c.id = new_msgs.conversation_id AND new_msgs.timestamp > p.last_read
LIMIT 0,10
Should I determine if the conversations is unread by doing an IF statement in MySQL or should I convert and compare timestamps on PHP?
Thanks again,
RS7
'p.last_read' as quoted above is a string constant - remove the quotes from this and see whether that changes anything, RS7. If user_id is an integer than remove the quotes from '5' as well.
As far as performance goes, ensure you have indexes on all the relevant columns. messages_participants.user_id and messages_message.timestamp being two important columns to index.
Yes, you have problem in your query.
Firstly, you should have noticed that you count the column you are grouping, so the count result will be 1.
Secondly, you are comparing the timestamp to a string : m.timestamp > 'p.last_read'.
Finally, avoid using LIMIT when you know your query will return one row (be self-confident :p).
Try:
SELECT
COUNT(m.conversation_id) AS count
FROM
messages_message m
INNER JOIN
messages_participants p ON p.conversation_id = m.conversation_id
WHERE
m.timestamp > p.last_read
AND p.user_id = 5
if you want to increase the query running time you can create a new index in message_participants (conversation_id, user_id) to index the conversations per users and then change your query with:
SELECT
COUNT(m.conversation_id) AS count
FROM
messages_message m
INNER JOIN
messages_participants p ON p.conversation_id = m.conversation_id AND p.user_id = 5
WHERE
m.timestamp > p.last_read
So that your DB engine can now filter the JOIN by simply looking at the index table. You could go deeper in this thought by indexing the timestampe too : (timestamp, conversation_id, user_id) and put the where condition in the join condition.
Whatever you choose, always put the most selective field first, to increase selectivity.
EDIT
First, let's comment your query:
SELECT
SQL_CALC_FOUND_ROWS c.*,
last_msg.*,
new_msgs.count as new_msgs_count
FROM
messages_conversation c
INNER JOIN
messages_participants p ON p.user_id = 5 -- Join with every conversations of user 5; if id is an integer, avoid writing '5' (string converted to an integer).
INNER JOIN
( -- Select every message : you could already select here messages from user 5
SELECT
*
FROM
messages_message m
ORDER BY -- this is not the goal of ORDER BY. Use MAX to obtain to latest timestamp.
m.timestamp DESC
LIMIT 1
) last_msg ON c.id = last_msg.conversation_id -- this query return one row and you want to have the latest timestamp for each conversation.
LEFT JOIN
(
SELECT
COUNT(m.id) AS count,
m.conversation_id,
m.timestamp
FROM
messages_message m
) new_msgs ON c.id = new_msgs.conversation_id AND new_msgs.timestamp > p.last_read
LIMIT 0,10
Let's rephrase your query:
select the number of new messages of a conversation subject, its last message and timestamp for user #id.
Do it step by step:
Selecting last message, timestamp in conversation for each user:
SELECT -- select the latest timestamp with its message
max(timestamp),
message
FROM
messages_message
GROUP BY
user_id
Aggregates functions (MAX, MIN, SUM, ...) work on the current group. Read this like "for each groups, calculate the aggregate functions, then select what I need where my conditions are true". So it will result in one row per group.
So this last query selects the last message and timestamp of every user in the messages_message table. As you can see, it is easy to select this value for a specific user adding the WHERE clause:
SELECT
MAX(timestamp),
message
FROM
messages_message
WHERE
user_id = #id
GROUP BY
user_id
Number of messages per conversation: for each conversation, count the number of messages
SELECT
COUNT(m.id) -- assuming id column is unique, otherwise count distinct value.
FROM
messages_conversation c
INNER JOIN -- The current user participated to the conversation
messages_participant p ON p.conversation_id = c.id AND p.user_id = #id
OUTER JOIN -- Messages of the conversation where the current user participated, newer than last read its time
messages_message m ON m.conversation_id = c.id AND m.timestamp > p.last_read = #id
GROUP BY
c.id -- for each conversation
INNER JOIN won't return rows for conversations where the current user did not participated.
Then OUTER JOIN will join with NULL columns if the condition is false, so that COUNT will return 0 - there is not new messages.
Putting it all together.
Select the last message and timestamp in conversation where the current user participated and the number of new messages in each conversation.
Which is a JOIN between the two last queries.
SELECT
last_msg.conversation_id,
last_msg.message,
last_msg.max_timestamp,
new_msgs.nb
FROM
(
SELECT
MAX(timestamp) AS max_timestamp,
message,
conversation_id
FROM
messages_message
WHERE
user_id = #id
GROUP BY
user_id
) last_msg
JOIN
(
SELECT
c.id AS conversation_id
COUNT(m.id) AS nb
FROM
messages_conversation c
INNER JOIN
messages_participant p ON p.conversation_id = c.id AND p.user_id = #id
OUTER JOIN
messages_message m ON m.conversation_id = c.id AND m.timestamp > p.last_read = #id
GROUP BY
C.id
) new_msgs ON new_msgs.conversation_id = last_msg.conversation_id
-- put here and only here a order by if necessary :)