I have this SQL table Messages
where the relevant entries are sender
, recipient
, timestamp
and text
. So for instance some examples would be
sender recipient text timestamp
user1 user2 hey 1
user2 user1 hello back 2
user1 user3 hey other dude 3
And in order to populate a view like in iMessage
for instance; I need an SQL query that for a given user gets the most recent message in each conversation that user is having. So currently what I have is
SELECT *
FROM Messages m
WHERE m.timestamp IN (SELECT MAX(m2.timestamp)
FROM Messages m2
WHERE m.sender = :user OR m.recipient = :user
GROUP BY sender, recipient)
But unfortunately this query returns all 3 of the messages above because the nested select groups the first two separately even though they're both in the same conversation. Is there a simple way to express what I really want in SQL, preferably without creating a Conversations table and doing some sort of joiny business?
Here in outer query you are trying to compare " m.id
" with inner query instead of using " m.id
" you should use " m.timestamp
"
SELECT * FROM Messages m WHERE m.timestamp IN (SELECT max(m2.timestamp) FROM Messages m2 WHERE m.sender = :user OR m.recipient = :user)
Your method is largely unworkable. Right now you are checking for WHERE m.id IN (SELECT MAX(m2.timestamp)
which is obviously not going to work.
Assuming it's a typo, and you meant to check WHERE m.timestamp IN ...
it's still not going to be reliable, because more than one message from different conversations may have the same timestamp.
Instead, let's abandon that method, because it's fraught with difficulties, and instead let's just perform an anti-join
.
select m.*
from messages m
left join messages m2
on ((m.sender = m2.sender and m.recipient = m2.recipient)
or (m.sender = m2.recipient and m.recipient = m2.sender))
and m.timestamp < m2.timestamp
where m2.sender is null
by joining on two conditions, we essentially join on 'conversation', whether either the sender and receiver are the same, or the sender of one is the receiver of the other. our final predicate there is a time range - when there doesn't exist a message with a higher timestamp, the outer join will return nulls, so we filter for those in the where
clause (this is the anti-join )
If you want to find last message of a user try this:
SELECT *
FROM `Messages`
WHERE `recipient` = :user OR `sender` = :user
ORDER BY `timestamp` DESC
LIMIT 1
And if you want to find last message between two users try this:
SELECT *
FROM `Messages`
WHERE
:user1 + :user2 =
CASE WHEN `recipient` = :user1 THEN `recipient`
WHEN `sender` = :user1 THEN `sender`
ELSE NULL
END +
CASE WHEN `recipient` = :user2 THEN `recipient`
WHEN `sender` = :user2 THEN `sender`
ELSE NULL
END
ORDER BY `timestamp` DESC
LIMIT 1
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.