查询每个对话中与每个其他用户的最新消息

时间:2015-04-16 15:01:57

标签: mysql sql

我有一个表messages用于用户之间的对话。 表格列的名称是:

messageID | fromUser | forUser | message | submitDate | seen

示例数据:

1  |  1 | 2 | "hi"             | "12341" | 0
2  |  2 | 1 | "hi"             | "12342" | 0
3  |  1 | 3 | "hi"             | "12343" | 0
4  |  1 | 4 | "hi 4"           | "12344" | 0
5  |  2 | 1 | "hello"          | "12345" | 0
6  |  1 | 2 | "hello how r u?" | "12346" | 0
7  |  3 | 1 | "hello user 1"   | "12345" | 0

如何编写查询以查找自己与系统中其他所有用户之间发送的最后一条消息? 我的意思是最后的消息是:

between user 1 and 2 : "hello how r u?"
between user 1 and 3 : "hello user 1"
between user 4 and 1 : "hi 4""

我的查询:

$query = "SELECT DISTINCT `fromUser`, `forUser`, `message`, `seen`, 
                          `username`, `userPhoto` 
          FROM `messages`,`user` 
          WHERE (`forUser`= '$myUserID' OR `fromUser`= '$myUserID')
                AND (((`forUser`= `userID`) AND (`forUser` !=  '$myUserID')) 
                   OR ((`fromUser`= `userID`) 
                   AND (`fromUser` !=  '$myUserID'))) 
          ORDER BY `submitDate` DESC";

但是此查询需要获取对话中的所有消息!我只需要最后一条消息。

4 个答案:

答案 0 :(得分:2)

如果messageIdauto_increment主键,则可以使用其值来区分每个会话中的最新消息。如果submitDate的类型为DATETIMETIMESTAMP,那么它将是另一种选择,但如果它的类型为DATE,则其分辨率不足。

但关键是识别和过滤最新消息的时间戳或ID。您可以使用合适的聚合(子)查询在每个会话的基础上识别ID或时间戳,并通过内部联接执行过滤,如下所示:

SELECT m.*
FROM
  messages m
  JOIN (
    SELECT
      MAX(messageId),
      CASE
        WHEN fromUser = '$myUserId' THEN forUser
        WHEN forUser = '$myUserId' THEN fromUser
      END AS otherUser  
    FROM messages
    GROUP BY
      CASE
        WHEN fromUser = '$myUserId' THEN forUser
        WHEN forUser = '$myUserId' THEN fromUser
      END
    HAVING otherUser IS NOT NULL
  ) other
    ON m.messageId = other.messageId

答案 1 :(得分:2)

您可以使用以下查询为每位用户与其他用户进行userID = 1的用户获取最新消息:

SELECT messageID, message, submitDate, otherUser
FROM (
   SELECT messageID, message, submitDate,
          @row_number:=CASE WHEN @other=otherUser THEN @row_number+1 
                            ELSE 1 
                       END AS row_number,
          @other:=otherUser AS otherUser             
   FROM (
      SELECT messageID, message, submitDate,
             IF(1 = fromUser, forUser, fromUser) as otherUser             
      FROM messages AS m
      WHERE 1 IN (fromUser,forUser) 
      ORDER BY otherUser, submitDate DESC) t ) s
WHERE s.row_number = 1

SQL Fiddle Demo

内部查询:

SELECT messageID, message, submitDate,
       IF(1 = fromUser, forUser, fromUser) as otherUser             
FROM messages AS m
WHERE 1 IN (fromUser,forUser) 
ORDER BY otherUser, submitDate DESC

用于返回用户userID = 1发送或接收的消息列表。计算列otherUser只包含与userID = 1用户对话中涉及的其他用户发件人或收件人

在外部查询中,使用变量@row_number来模拟SQL Server中可用的ROW_NUMBER() OVER (PARTITION BY otherUser ORDER BY submitDate DESC)

最后,外部查询使用@row_number计算的数字来仅选择otherUser的最新邮件。

答案 2 :(得分:1)

尝试这个简单易用的方法,它也会找到每个组的用户查看我的代码: -

{{1}}

答案 3 :(得分:0)

事实证明,我已经answered a nearly identical question on Code Review

SELECT DISTINCT通常表明你做错了什么。几乎总是可以在不需要DISTINCT的情况下制定更好的查询。

一个有用的技巧是在对话者和自我方面重新标记发件人和收件人。然后,过滤结果只是一个简单的问题。仅保留self是相关用户的邮件。然后,具有相同interlocutor的每一行在概念上构成一个线程。

SELECT  fromUser, forUser, message, seen, otherUser.username, otherUser.userPhoto
    FROM
        (   -- Messages I sent
            SELECT *, forUser AS interlocutor, fromUser AS self
                FROM messages
            UNION
            -- Messages I received
            SELECT *, fromUser AS interlocutor, forUser AS self
                FROM messages
        ) AS thread_latest
        INNER JOIN users AS otherUser
            ON otherUser.userID = interlocutor
    WHERE
        self = '$myUserID' AND
        -- Discard all but the latest message in each thread
        NOT EXISTS (
            SELECT messageID
                (
                    SELECT *, forUser AS interlocutor, fromUser AS self
                        FROM messages
                    UNION
                    SELECT *, fromUser AS interlocutor, forUser AS self
                        FROM messages
                ) AS thread_later
                WHERE
                    thread_later.self = thread_latest.self AND
                    thread_later.interlocutor = thread_latest.interlocutor AND
                    thread_later.submitDate > thread_latest.submitDate
            )
    ORDER BY submitDate DESC;

请注意,有一个子查询出现两次。我们可以通过创建视图来使其更清晰。

CREATE VIEW threads AS
    -- Messages I sent
    SELECT *, forUser AS interlocutor, fromUser AS self
        FROM messages
    UNION
    -- Messages I received
    SELECT *, fromUser AS interlocutor, forUser AS self
        FROM messages;

SELECT  fromUser, forUser, message, seen, otherUser.username, otherUser.userPhoto
    FROM
        threads AS thread_latest
        INNER JOIN users AS otherUser
            ON otherUser.userID = interlocutor
    WHERE
        self = '$myUserID' AND
        -- Discard all but the latest message in each thread
        NOT EXISTS (
            SELECT messageid
                FROM threads AS thread_later
                WHERE
                    thread_later.self = thread_latest.self AND
                    thread_later.interlocutor = thread_latest.interlocutor AND
                    thread_later.time > thread_latest.time
            )
    ORDER BY submitDate DESC;

我将借此机会指出这个查询是PostgreSQL真正发挥作用的地方。 PostgreSQL中的两个功能(自8.4版本起)使其变得简单。 WITH clause允许您在查询本身中定义帮助器视图。更重要的是, window functions让你通过对话者对线程进行分区,这正是这个问题的棘手部分。

WITH threads(messageID, fromUser, forUser, message, submitDate, interlocutor, self) AS (
    -- Messages I sent
    SELECT *, forUser, fromUser FROM messages
    UNION
    -- Messages I received
    SELECT *, fromUser, forUser FROM messages
), myThreads AS (
    SELECT  *,
            ROW_NUMBER() OVER (PARTITION BY interlocutor ORDER BY submitDate DESC) AS thread_pos
        FROM threads
        WHERE self = $myUserId
)
SELECT  fromUser, forUser, message, seen, otherUser.username, otherUser.userPhoto
   FROM my_threads
        INNER JOIN users AS otherUser
            ON otherUser.userID = interlocutor
    WHERE thread_pos = 1 -- Only the latest message per thread
    ORDER BY submitDate DESC;

其他数据库(如MS SQL Server和Oracle)也支持窗口查询。如果你正在做任何涉及SQL的事情,请考虑从MySQL切换。