MYSQL - Get Last messages MAX(time) by users - mysql

A conversation contains messages and one message can be send to 2 or more users.
The goal is to retrieve the last messages by user for each conversation, so where the messages in each conversation are the newest.
Also the message_user.time is when the user has read the message so when it's equals to 0 it's means the message has not been read yet so it'll be even better if the first order will be the message_user where time is equal to 0 and the the message.time that are the biggest numbers.
I got those tables
Message User
user message time
-----|------------|-------
7 | 1 | 0
8 | 1 | 0
7 | 2 | 300
8 | 2 | 300
7 | 3 | 400
Message
id text conversation time
---|-------------|--------------|----------
1 | blah blah1 | 1 | 200
2 | blah blah2 | 1 | 300
3 | blah blah3 | 2 | 400
4 | blah blah4 | 2 | 500
The goal is to group the messages by conversation and then get the last message_user linked to that conversation that have the bigger timestamp first.
What i tried is this but i don't get the last messages (wrong order)
SELECT m.user, mu.message, mu.time, mu.id, m.text, m.time as message_time, m.conversation
FROM message_user as mu,message as m
WHERE mu.message=m.id AND mu.user=8
GROUP BY m.conversation
ORDER BY m.time DESC';
Then the next step will be:
I don't know if it's possible but it if the order by can ouput the message_user where the time is equal to 0 first and then second order is message.time (i don't even know if it's possible in one request that will be perfect!)
For the user 8 the output should be this :
text conversation
-----------|------------
blah blah1 | 1 //because message_user.time = 0 (means message is unread)
blah blah3 | 2 //because message.time is the highest in the conversation
Thank you !

First, I'd suggest restructuring your tables to make this easier, but here's what I think you are asking for... Each conversation, showing only the last conversion message created, with the last time and user who read it (but prefer unread over this).
SELECT m.user as User_Sent, m.conversation, m.text, m.time as Time_Sent
, mu.id, mu.user as User_Read, mu.time as Time_Read
FROM message as m
JOIN (
SELECT mx.conversation, MAX(mx.time) as MaxTime
FROM message as mx GROUP BY mx.conversation
) as mx
On m.conversation = mx.conversation
And m.time = mx.MaxTime
JOIN (
SELECT mu.message, MAX(mu.time) as MaxTime, MIN(mu.time) as MinTime
From message_user as mu
GROUP BY mu.message
) as mux
On m.id = mux.message
JOIN message_user as mu
ON m.id = mu.message
AND CASE WHEN mux.MinTime = 0 THEN mux.MinTime ELSE mux.MaxTime END = mu.time

Related

MySQL: Get latest message from 2 tables that are associated with eachother

First, thank you so much for your help.
I have 2 tables: a conversation table and a message table, and a third table assoc_message__conversation that associates the messages to a conversation.
I need to get the latest message_id and message sent for each conversation specified, along with the conversation_id it is associated with.
Here is a db-fiddle: https://www.db-fiddle.com/f/kxRQeGUYYgQ7FTwi96hbLp/0
As you can see in this example, there are two conversations with conversation_id of 1 and 2, and there are three messages associated to each conversation. Messages 1, 2, and 3 are associated to conversation 1, and messages 4, 5 and 6 are associated to conversation 2.
I need to be able to specify the conversation_id's in the assoc_message__conversation table (IDs 1 and 2), and retrieve the latest message_id, message and the associated conversation_id sent from the message table for each conversation specified.
So the rows it should pull are:
conversation_id | message_id | message
------------------------------------------------
1 | 3 | "Latest message"
------------------------------------------------
2 | 6 | "Latest message"
------------------------------------------------
Thank you so much for your help!
In older versions of MySQL (< 8.0.2), we can use Derived Tables. In a Derived table, we can get the latest send_datetime value for each conversation_id. Also, it is noteworthy that you can provide your filters for conversation_id in the WHERE clause of this subquery.
We can then use this subquery`s result-set and join back to the main tables appropriately, to get the row corresponding to the latest message in a conversation.
Schema (MySQL v5.7)
View on DB Fiddle
Query #1
SELECT
amc.conversation_id,
m.message_id,
m.message
FROM
assoc_message__conversation AS amc
JOIN message AS m
ON m.message_id = amc.message_id
JOIN
(
SELECT
amc1.conversation_id,
MAX(m1.send_datetime) AS latest_send_datetime
FROM
assoc_message__conversation AS amc1
JOIN message AS m1
ON m1.message_id = amc1.message_id
WHERE amc1.conversation_id IN (1,2) -- Here you provide your input filters
GROUP BY amc1.conversation_id
) AS dt
ON dt.conversation_id = amc.conversation_id AND
dt.latest_send_datetime = m.send_datetime;
Result
| conversation_id | message_id | message |
| --------------- | ---------- | -------------- |
| 1 | 3 | Latest message |
| 2 | 6 | Latest message |
In MySQL 8.0.2 and above, we can use Row_Number() functionality. Within a partition of conversation_id, we will determine Row Number for every message, sorted in descending order of send_datetime. In this subquery, you can provide your filters for conversation_id in the WHERE clause.
We will then use this result-set as a Derived Table, and consider only those rows, where Row Number value is 1 (as it will belong to latest send_datetime).
Schema (MySQL v8.0)
View on DB Fiddle
Query #2
SELECT
dt.conversation_id,
dt.message_id,
dt.message
FROM
(
SELECT
amc.conversation_id,
m.message_id,
m.message,
ROW_NUMBER() OVER (PARTITION BY amc.conversation_id
ORDER BY m.send_datetime DESC) AS row_no
FROM
assoc_message__conversation AS amc
JOIN message AS m
ON m.message_id = amc.message_id
WHERE amc.conversation_id IN (1,2) -- Here you provide your input filters
) AS dt
WHERE dt.row_no = 1;
Result
| conversation_id | message_id | message |
| --------------- | ---------- | -------------- |
| 1 | 3 | Latest message |
| 2 | 6 | Latest message |
Assuming that amc_id increments for each new message, I would recommend a correlated subquery in the where clause:
select amc.*, m.message
from message m join
assoc_message__conversation amc
on amc.message_id = m.message_id
where amc.amc_id = (select max(amc.amc_id)
from assoc_message__conversation amc2
where amc2.conversation_id = amc.conversation_id
);
If you actually need to use send_datetime, then an additional join is necessary:
where m.send_datetime = (select max(m2.send_datetime)
from message m2 join
assoc_message__conversation amc2
on amc2.message_id = m2.message_id
where amc2.conversation_id = amc.conversation_id
)

PHP MySQL SELECT newest entry grouped by an ID and ordered by timestamp

I am working on an problem regarding Selecting data from two MySQL tables.
First table holds messages | messages | (id, msg_group_id, to_user_id, from_user_id, datetime)
Second table holds user data | profiles | (user_id, name, firstname, ...)
ATM it works the way, that I can select ALL messages with a certain 'to_id' and by adding a JOIN statement getting the name and firstname of the user who sends the message.
My problem now is that I can not figure out a way to ONLY select the newest message of a certain msg_group_id.
I already tried GROUP BY msg_group_id combined with ORDER BY datetime DESC.
But that only throws the very first entry in message table. But I want to last one. :-)
I hope you can help me. :-)
My actual SQL statement:
SELECT LEFT(messages.message, 10) AS message,
`messages`.`msg_group_id`,
`messages`.`datetime`,
`profiles`.`name`,
`profiles`.`firstname`
FROM `messages`
LEFT JOIN `profiles`
ON `messages`.`from_user_id` = `profiles`.`user_id`
WHERE `to_user_id` = '2'
ORDER BY `datetime` DESC
LIMIT 20;
Thanks in Advance
Sample INPUT:
[messages]
|id|msg_group_id|to_user_is|from_user_id|message |datetime|
0 | 1 | 1 | 2 | Hello World1 | 2015-12-21 10:42:00
1 | 1 | 1 | 2 | Hello World2 | 2015-12-21 10:43:00
2 | 1 | 1 | 2 | Hello World3 | 2015-12-21 10:44:00
[profiles]
user_id|name |firstname|
1 | Test | User
2 | Thanks | Worldname
Result (what I don't want)
message|msg_group_id|datetime|name|firstname
Hello World1 | 1 | 2015-12-21 10:42:00 | Thanks | Worldname
Result (what I want)
message|msg_group_id|datetime|name|firstname
Hello World3 | 1 | 2015-12-21 10:44:00 | Thanks | Worldname
May be this query can help:
SELECT m.message, m.msg_group_id, m.datetime, u.name, u.firstname
FROM message as m, profiles as u
WHERE m.from_user_id = u.user_id
GROUP BY m.msg_group_id
ORDER BY m.datetime DESC
Or use INNER JOIN
SELECT m.message, m.msg_group_id, m.datetime, u.name, u.firstname
FROM message as m
INNER JOIN profiles as u ON m.from_user_id = u.user_id
GROUP BY m.msg_group_id
ORDER BY m.datetime DESC
I guess I solved the Problem with the help of another thread:
https://stackoverflow.com/a/1313140/4493030
My SQL Statement as follows:
SELECT `messages`.*, `profiles`.`nick_name`
FROM `messages`
LEFT JOIN `profiles`
ON `messages`.`from_user_id` = `profiles`.`user_id`
INNER JOIN
(SELECT konversation_id, MAX(id) AS maxid FROM messages
WHERE messages.to_user_id = 2
GROUP BY konversation_id) AS b
ON messages.id = b.maxid
WHERE `to_user_id` = '2'
ORDER BY `datetime` DESC
LIMIT 20;
Thanks to all of you who tried to help.
I found a way to tight it down
SELECT messages.to_user_id, messages.msg_group_id, MAX(messages.id) AS maxid, messages.from_user_id, profiles.name
FROM messages
LEFT JOIN profiles
ON messages.from_user_id = profiles.user_id
WHERE messages.to_user_id = 2
GROUP BY msg_group_id

Select most recent record based on two conditions

I have user1 who exchanged messages with user2 and user4 (these parameters are known). I now want to select the latest sent or received message for each conversation (i.e. LIMIT 1 for each conversation).
SQLFiddle
Currently my query returns all messages for all conversations:
SELECT *
FROM message
WHERE (toUserID IN (2,4) AND userID = 1)
OR (userID IN (2,4) AND toUserID = 1)
ORDER BY message.time DESC
The returned rows should be messageID 3 and 6.
Assuming that higher id values indicate more recent messages, you can do this:
Find all messages that involve user 1
Group the results by the other user id
Get the maximum message id per group
SELECT *
FROM message
WHERE messageID IN (
SELECT MAX(messageID)
FROM message
WHERE userID = 1 -- optionally filter by the other user
OR toUserID = 1 -- optionally filter by the other user
GROUP BY CASE WHEN userID = 1 THEN toUserID ELSE userID END
)
ORDER BY messageID DESC
Updated SQLFiddle
You can do this easily by separating it into two queries with ORDER BY and LIMIT then joining them with UNION:
(SELECT *
FROM message
WHERE (toUserID IN (2,4) AND userID = 1)
ORDER BY message.time DESC
LIMIT 1)
UNION
(SELECT *
FROM message
WHERE (userID IN (2,4) AND toUserID = 1)
ORDER BY message.time DESC
LIMIT 1)
The parenthesis are important here, and this returns messages 2 and 6, which seems correct, not 3 and 6.
It also seems like you could use UNION ALL for performance instead of UNION because there won't be duplicates between the two queries, but it's better if you decide that.
Here's your data:
MESSAGEID USERID TOUSERID MESSAGE TIME
1 1 2 nachricht 1 123
2 1 2 nachricht 2 124
3 2 1 nachricht 3 125
4 3 2 nachricht wrong 1263
5 2 4 nachricht wrong 1261
6 4 1 nachricht sandra 126
The below works as required:
SELECT m1.*
FROM Message m1
LEFT JOIN Message m2
ON LEAST(m1.toUserID, m1.userID) = LEAST(m2.toUserID, m2.userID)
AND GREATEST(m1.toUserID, m1.userID) = GREATEST(m2.toUserID, m2.userID)
AND m2.time > m1.Time
WHERE m2.MessageID IS NULL
AND ( (m1.toUserID IN (2,4) AND m1.userID = 1)
OR (m1.userID IN (2,4) AND m1.toUserID = 1)
);
To simplify how this works, imagine you just wanted the latest message sent by userid 1, rather than having to match the to/from tuples as this adds clutter to the query that doesn't help. To get this I would use:
SELECT m1.*
FROM Message AS m1
LEFT JOIN Message AS m2
ON m2.UserID = m1.UserID
AND m2.time > m1.time
WHERE m1.UserID = 1
AND m2.MessageID IS NULL;
So, we are joining similar messages, stipulating that the second message (m2) has a greater time than the first, where m2 is null it means there is not a similar message with a later time, therefore m2 is the latest message.
Exactly the principal has been applied in the solution, but we have a more complicated join to link conversations.
I have used LEAST and GREATEST in the join, the theory being that since you have 2 members in your tuple (UserID, ToUserID), then in any combination the greatest and the least will be the same, e.g.:
From/To | Greatest | Least |
--------+-----------+-------+
1, 2 | 2 | 1 |
2, 1 | 2 | 1 |
1, 4 | 4 | 1 |
4, 1 | 4 | 1 |
4, 2 | 4 | 2 |
2, 4 | 4 | 2 |
As you can see, in similar From/To the greatest and the least will be the same, so you can use this to join the table to itself.
There are two parts of your query in the following order:
You want the latest outgoing or incoming message for a conversation between two users
You want these latest messages for two different pairs of users, i.e. conversations.
So, lets get the latest message for a conversation between UserID a and UserID b:
SELECT *
FROM message
WHERE (toUserID, userID) IN ((a, b), (b, a))
ORDER BY message.time DESC
LIMIT 1
Then you want these to be combined for the two conversations between UserIDs 1 and 2 and UserIDs 1 and 4. This is where the union comes into play (we do not need to check for duplicates, thus we use UNION ALL, thanks to Marcus Adams, who brought that up first).
So a complete and straightforward solution would be:
(SELECT *
FROM message
WHERE (toUserID, userID) IN ((2, 1), (1, 2))
ORDER BY message.time DESC
LIMIT 1)
UNION ALL
(SELECT *
FROM message
WHERE (toUserID, userID) IN ((4, 1), (1, 4))
ORDER BY message.time DESC
LIMIT 1)
And as expected, you get message 3 and 6 in your SQLFiddle.

How to group by rows but with the most recent date?

In my MySQL database I have a table like this used for storing conversation messages from any people
id int(11) id of the message
from member_id int(11) id of the person the message was sent from
to member_id int(11) id of the person the message was sent to
date sent datetime date of when it was sent
active tinyint(1) if the message is deleted
text longtext the text of the message
from_read tinyint(1) boolean to know if the person who sent it read it
to_read tinyint(1) boolean to know if the person who it got sent to read it
So for example, it could have like:
from_member_id to_member_id date sent
1 2 june 12
1 3 june 13
2 3 june 14
3 1 june 9
So we have a conversation between person 1 and 2, 1 and 3, 2 and 3.
I am trying to get a select statement which will give me the most recent message that the current user is involved with from every conversation that user is in. So if 1 is logged in then I would expect to get 2 rows. The first row in the result set would be the second row above (july 13) because its the most recent, then then the second row in the result set would be the first row above (june 12), which are the most recent from 1's two conversations. The result set also needs to be sorted by date sent, so newer conversations are listed on top.
What I am trying to do is like the texting in android phones, where you see the list of conversations, and the most recent message in each listing.
This is my sql query
SELECT *
FROM (
SELECT *
FROM message
WHERE `from member_id`=1 OR `to member_id`=1
ORDER BY IF(`from member_id`=1, `to member_id`, `from member_id`)
) as t
GROUP BY IF(`from member_id`=1, `to member_id`, `from member_id`)
I just hardcoded 1 for now to be the current user. What I am doing is, sorting them by the id of the other person which I can check using the if statement, then grouping that result so I try to get the recent one from each conversation.
The problem is that when grouping, each group can have more than 1 rows, and it just seems to pick some random row. How can I get it to pick the row that has the most recent date sent value?
Are you looking for something like this?
SELECT m.*
FROM message m JOIN
(
SELECT from_member_id, to_member_id, MAX(date_sent) date_sent
FROM message
WHERE from_member_id = 1
GROUP BY from_member_id, to_member_id
) q
ON m.from_member_id = q.from_member_id
AND m.to_member_id = q.to_member_id
AND m.date_sent = q.date_sent
ORDER BY date_sent DESC
Sample output:
| FROM_MEMBER_ID | TO_MEMBER_ID | DATE_SENT |
----------------------------------------------
| 1 | 3 | 2013-06-13 |
| 1 | 2 | 2013-06-12 |
Here is SQLFiddle demo
UPDATE
SELECT m.*
FROM message m JOIN
(
SELECT LEAST(from_member_id, to_member_id) least_id,
GREATEST(from_member_id, to_member_id) greatest_id,
MAX(date_sent) date_sent
FROM message
WHERE from_member_id = 1
OR to_member_id = 1
GROUP BY LEAST(from_member_id, to_member_id),
GREATEST(from_member_id, to_member_id)
) q
ON LEAST(m.from_member_id, m.to_member_id) = q.least_id
AND GREATEST(m.from_member_id, m.to_member_id) = q.greatest_id
AND m.date_sent = q.date_sent
ORDER BY date_sent DESC
Sample output:
| FROM_MEMBER_ID | TO_MEMBER_ID | DATE_SENT |
----------------------------------------------
| 3 | 1 | 2013-06-14 |
| 1 | 2 | 2013-06-12 |
Here is SQLFiddle demo
SELECT
*
FROM message m INNER JOIN
(
SELECT
from_menber_id,
MAX(date_sent) AS sentdate
FROM message s
GROUP BY from_menber_id
) AS a
ON m.date_sent = a.sentdate AND a.from_menber_id = m.from_menber_id

Displaying messsage in specific format in mysql php

I have created a messaging system for users which will allow users to send message to another.
For this I have created two tables.
conversation(conversation_id,user_id1,user_id2)
messages(message_id,conversation_id,sender_id,receiver_id,message,created_time)
If users are talking first time, a conversation_id will be created with user_id1(who initiate chat) and user_id2(to who user_id1 is sending message)
Messages table will contain all info related to message.
Now what I want is, to create a message summary page where logged in user can view his all conversation list between other users order by created_time, group by conversation_id.
Here are table data:
conversation_id | user_id1 | user_id2
1 100 103
2 101 103
3 103 102
message_id| conversation_id| sender_id| receiver_id| message | created_time
1 1 100 103 MSG A 2012-06-08 08:38:57
2 1 103 100 MSG B 2012-06-08 08:39:40
3 2 101 103 MSG C 2012-06-08 08:40:20
4 3 102 103 MSG D 2012-06-08 08:41:10
And here is what output what I am looking for: Lets say logged in user is with id: 103
conversation_id| conversation_with | last_message | created_time
3 102 MSG D 2012-06-08 08:41:10
2 101 MSG C 2012-06-08 08:40:20
1 100 MSG B 2012-06-08 08:39:40
So this output is ordering by created_time, Grouping by conversation_id and displaying id of user in conversation_with , with who userid 103 is having conversation.
Can somebody provide MySQL query I will need to get this output.
This was a fun one. So, you have three main things to solve.
Alternate the conversation_with column depending on where the desired user shows up amongst two columns.
greatest-n-per-group to find the most recent message.
least-n-per-group to find the earliest message created_time.
The first one I solved by querying twice, alternating the user_id column, unioning the results, and then ordering the unioned results by created_time. I think I can solve this with one query and no unions, but it works for now.
2 and 3 are a little more involved. Here's a SQL fiddle with the query: http://sqlfiddle.com/#!2/bf2b7/1
TL;DNR
select * from (
select c.conversation_id, c.user_id2 as conversation_with, m1.message as last_message, m3.created_time
from conversation as c
join messages as m1 on c.conversation_id = m1.conversation_id
left outer join messages as m2 on (c.conversation_id = m2.conversation_id
and (m1.created_time < m2.created_time OR m1.created_time = m2.created_time AND m1.message_id < m2.message_id))
join messages as m3 on c.conversation_id = m3.conversation_id
left outer join messages as m4 on (c.conversation_id = m4.conversation_id
and (m3.created_time > m4.created_time OR m3.created_time = m4.created_time AND m3.message_id > m4.message_id))
where user_id1 = 103 and m2.message_id is null and m4.message_id is null
union all
select c.conversation_id, c.user_id1 as conversation_with, m1.message as last_message, m3.created_time
from conversation as c
join messages as m1 on c.conversation_id = m1.conversation_id
left outer join messages as m2 on (c.conversation_id = m2.conversation_id
and (m1.created_time < m2.created_time OR m1.created_time = m2.created_time AND m1.message_id < m2.message_id))
join messages as m3 on c.conversation_id = m3.conversation_id
left outer join messages as m4 on (c.conversation_id = m4.conversation_id
and (m3.created_time > m4.created_time OR m3.created_time = m4.created_time AND m3.message_id > m4.message_id))
where user_id2 = 103 and m2.message_id is null and m4.message_id is null
) as conversations
order by created_time desc