'How to get the latest message in each conversation of a certain user in SQL?

I need to write a query that returns the latest message in a conversation between two users. I've included the schema and my (failed) attempts in this fiddle: http://sqlfiddle.com/#!15/322c3/11

I've been working around the problem for some time now but every time I run any of my ugly queries a sweet little kitten dies.

Any help would be much appreciated. Do it for the kittens.



Solution 1:[1]

... the latest message in a conversation between two users.

Assuming the users with ID 1 and 3, like you did in the fiddle, we are interested in the message with the latest created_at and (sender_id, receiver_id) being (1,3) or (3,1).

You can use ad-hoc row types to make the syntax short:

SELECT * 
FROM   messages 
WHERE  (sender_id, receiver_id) IN ((1,3), (3,1))
ORDER  BY created_at DESC
LIMIT  1;

Or explicitly (and slightly faster, also easier to use with indexes):

SELECT * 
FROM   messages 
WHERE (sender_id = 1 AND receiver_id = 3 OR
       sender_id = 3 AND receiver_id = 1)
ORDER  BY created_at DESC
LIMIT  1;

For all conversations of a user

Added solution as per request in comment.

SELECT DISTINCT ON (user_id) *
FROM (
   SELECT 'out' AS type, id, receiver_id AS user_id, body, created_at
   FROM   messages 
   WHERE  sender_id = 1
  
   UNION  ALL
   SELECT 'in' AS type, id, sender_id AS user_id, body, created_at
   FROM   messages 
   WHERE  receiver_id = 1
   ) sub
ORDER  BY user_id, created_at DESC;

The approach here is to fold foreign sender / receiver into one column to simplify the extraction of the last row.

Detailed explanation for DISTINCT ON in this related answer:

sqlfiddle - with improved and simplified test case

Solution 2:[2]

This provides the latest message between two users, regardless of message direction:

  SELECT Distinct mes.ID, sendu.Username AS Sender,
  recu.Username as Receiver, Body, maxSent as TimeSent

  FROM messages mes

  INNER JOIN 

  (
    SELECT One, Two, MAX(CREATED_AT) maxSent
    FROM
    (
      SELECT  'Sender' as type, Sender_ID AS One, receiver_id as Two,created_At
      FROM messages 

      UNION ALL

      SELECT 'Receiver' as type, receiver_id AS One, Sender_ID as Two ,created_At
      FROM messages 
    ) a

    Group By One,Two
  ) b

  ON mes.created_at = b.maxSent

  INNER JOIN users sendu
  ON sendu.ID = mes.Sender_ID

  INNER JOIN users recu
  ON recu.ID = mes.Receiver_ID

It does not separate 'conversations', but there is nothing to signify that. Perhaps if you also include a message header or title field this will be possible.

Solution 3:[3]

SELECT * 
FROM messages 
WHERE (sender_id = 1 AND receiver_id = 2) 
   OR (sender_id = 2 AND receiver_id = 1) 
ORDER BY created_at DESC
LIMIT 1;

Solution 4:[4]

Try this and see

SELECT  *
FROM    (SELECT * ,ROW_NUMBER() OVER(ORDER BY created_at DESC) as RowID
FROM   messages 
WHERE  (sender_id, receiver_id) IN ((1,3), (3,1))
    ) sub
WHERE   RowID = 1

Solution 5:[5]

use this --> select * from messages order by created_at desc limit 1

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 user3056839
Solution 3
Solution 4
Solution 5 HarshSharma