简体   繁体   中英

How do I get the most recently-created rows where one of several columns has a certain value?

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

demo here

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM