SQL GROUP BY实现会话收件箱:获取各对话最新消息
按对话线程获取最新消息的直接解法
你的核心问题是要把(SenderId=1, ReceiverId=2)和(SenderId=2, ReceiverId=1)视为同一个对话线程,同时取出每个线程的最新消息。原来的SQL因为分组维度不对,且无法保证取到最新数据,所以达不到效果。下面是两种更直接的解决方案:
方法一:使用窗口函数(推荐,现代SQL支持)
利用ROW_NUMBER()窗口函数,先给每个统一后的对话线程的消息按时间倒序编号,最新消息的编号为1,最后筛选出这条即可:
WITH thread_messages AS ( SELECT *, -- 按统一后的对话线程分组,按时间倒序排号 ROW_NUMBER() OVER ( PARTITION BY LEAST(SenderId, ReceiverId), GREATEST(SenderId, ReceiverId) ORDER BY ts DESC ) AS rn FROM message -- 过滤当前用户参与的对话 WHERE SenderId = {current_user_id} OR ReceiverId = {current_user_id} ) -- 取每个线程的第一条(最新)消息 SELECT Id, Body, ts, SenderId, ReceiverId FROM thread_messages WHERE rn = 1;
LEAST()和GREATEST()函数会把对话双方的ID按大小排序,不管谁发谁收,都能得到同一个线程标识(比如(1,2)和(2,1)都会变成1和2)。
方法二:分组关联(兼容老版本SQL)
如果你的数据库不支持窗口函数,可以先分组找到每个线程的最新时间,再关联原表获取对应消息:
SELECT m.* FROM message m INNER JOIN ( SELECT LEAST(SenderId, ReceiverId) AS user1, GREATEST(SenderId, ReceiverId) AS user2, MAX(ts) AS latest_ts FROM message WHERE SenderId = {current_user_id} OR ReceiverId = {current_user_id} GROUP BY user1, user2 ) t ON LEAST(m.SenderId, m.ReceiverId) = t.user1 AND GREATEST(m.SenderId, m.ReceiverId) = t.user2 AND m.ts = t.latest_ts;
如果同一个线程在同一时间有多条消息,这个查询会返回所有符合的记录,若只需一条,可以在子查询里同时取MAX(Id),并在关联条件里加上m.Id = t.latest_id。
原SQL的问题说明
你原来的GROUP BY SenderId, ReceiverId会把(1,2)和(2,1)分成两个独立分组,无法识别为同一个线程;同时SELECT *在标准SQL中不符合分组规则(仅部分数据库宽松模式允许),且无法保证取到分组内的最新消息,因为非聚合列的返回结果是不确定的。
内容的提问来源于stack exchange,提问作者digiadit
相关产品推荐
相关产品推荐

