【问题标题】:SQL query for most recent messages最新消息的 SQL 查询
【发布时间】:2012-08-19 14:14:05
【问题描述】:

我正在尝试实现一个与 facebook 非常相似的消息系统。消息表是:

+--------+----------+--------+-----+----------+
| msg_id | msg_from | msg_to | msg | msg_time |
+--------+----------+--------+-----+----------+

这里msg_frommsg_to 包含用户ID,msg_time 包含消息的时间戳。一个用户的用户 ID 可以同时出现在 to 和 from 列中,并且对于另一个用户可以多次出现。我应该如何编写一个 SQL 查询来选择两个用户之间最近发送的消息? (消息可以来自任何一个) 1 to 2 或 2 to 1 。

【问题讨论】:

    标签: mysql


    【解决方案1】:

    既然 John Woo 澄清它不是定向的,这是我的新答案:

    select *
    from msgsList
    where (least(msg_from, msg_to), greatest(msg_from, msg_to), msg_time)       
    in 
    (
        select 
           least(msg_from, msg_to) as x, greatest(msg_from, msg_to) as y, 
           max(msg_time) as msg_time
        from msgsList 
        group by x, y
    );
    

    输出:

    | MSG_ID | MSG_FROM | MSG_TO |    MSG |                       MSG_TIME |
    ------------------------------------------------------------------------
    |      1 |        1 |      2 |  hello | January, 23 2010 17:00:00-0800 |
    |      5 |        1 |      3 | me too | January, 23 2012 00:15:00-0800 |
    |      6 |        3 |      2 |  hello | January, 23 2012 01:12:12-0800 |
    

    对于这个输入:

    create table msgsList
    (
      msg_id int,
      msg_from int, 
      msg_to int,
      msg varchar(10),
      msg_time datetime
    );
    
    insert into msgslist VALUES
    
    (1, 1, 2, 'hello', '2010-01-23 17:00:00'),      -- shown
    (2, 2, 1, 'world', '2010-01-23 16:00:00'),
    
    (3, 3, 1, 'i am alive', '2011-01-23 00:00:00'),
    (4, 3, 1, 'really', '2011-01-22 23:15:00'),
    (5, 1, 3, 'me too', '2012-01-23 00:15:00'),     -- shown
    
    (6, 3, 2, 'hello', '2012-01-23 01:12:12');      -- shown
    

    SQLFiddle Demo


    如果 ANSI SQL 是您的理想之选,那么可以这样做:http://sqlfiddle.com/#!2/0a575/19

    select *
    from msgsList z
    where exists
    (
        select null
        from msgsList
        where 
          least(z.msg_from, z.msg_to) = least(msg_from, msg_to)
          and greatest(z.msg_from, z.msg_to) = greatest(msg_from, msg_to)
        group by least(msg_from, msg_to), greatest(msg_from, msg_to)
        having max(msg_time) = z.msg_time  
    ) ;
    

    【讨论】:

    • +1 用于使用 LEASTGREATEST
    • 像梦一样工作!!非常感谢!!
    【解决方案2】:

    就这么简单吗? http://www.sqlfiddle.com/#!2/50f9f/1

    set @User1 := 'John';
    set @User2 := 'Paul';
    
    
    select *
    from
    (
      select *
      from messages 
      where msg_from = @User1 and msg_to = @User2
      order by msg_time desc
      limit 1
    ) as x
    union
    select *
    from
    (
      select *
      from messages 
      where msg_from = @User2 and msg_to = @User1
      order by msg_time desc
      limit 1
    ) as x
    order by msg_time desc
    

    输出:

    | MSG_ID | MSG_FROM | MSG_TO |         MSG |                      MSG_TIME |
    ----------------------------------------------------------------------------
    |      2 |     Paul |   John | Hey Johnny! | August, 20 2012 00:00:00-0700 |
    |      1 |     John |   Paul | Hey Paulie! | August, 19 2012 00:00:00-0700 |
    

    如果只有 MySQL 支持窗口函数会简单很多:http://www.sqlfiddle.com/#!1/e4781/8

    with recent_message as
    (
    select *, rank() over(partition by msg_from, msg_to order by msg_time desc) as r
    from messages
    )
    select * 
    from recent_message 
    where r = 1 
        and 
        (
          (msg_from = 'John' and msg_to = 'Paul') 
          or
          (msg_from = 'Paul' and msg_to = 'John')
        )
    order by msg_time desc;
    

    【讨论】:

    • 这条sql语句能回答这组记录吗? SQFiddle link
    • 嗯.. 我读到 OP 的要求是只返回两个用户之间的最近对话:-) 因此只需要两个参数
    • 看到您的查询HERE,它应该只返回msgid = 2,因为它是最新的,并且消息可以通过John to PaulPaul to John 发送
    • 无论如何,我明白你在 SQLFiddle 链接上的意思:sqlfiddle.com/#!2/9bd90/1 我会想一个解决方案 :-)
    • 不要将此视为负面评论,但我认为 OP 需要从用户那里获取所有消息。如果他只需要两个值,我明白你的意思。如果他想像我提供的链接一样检索许多用户的对话怎么办?老实说,我一直在搜索 sn-ps 如何获得 peter and northnorth and peter 相同的结果:D 顺便说一句,你是 pinoy 吗?呵呵
    【解决方案3】:

    对于像这样的任何复杂查询,请使用 TDQD — 测试驱动的查询设计。逐步设计答案,步骤的大小取决于您的经验以及您对问题的理解程度。

    第 1 步 - 查找给定用户之间最近一条消息的时间

    在整个过程中,我假设用户 ID 是整数;我使用的值是 1000 和 2000。

    SELECT MAX(msg_time) AS msg_time
      FROM message
     WHERE ((msg_to = 1000 AND msg_from = 2000) OR
            (msg_to = 2000 AND msg_from = 1000)
           )
    

    第 2 步 — 查找与最新消息对应的记录

    SELECT m.*
      FROM message AS m
      JOIN (SELECT MAX(msg_time) AS msg_time
              FROM message
             WHERE ((msg_to = 1000 AND msg_from = 2000) OR
                    (msg_to = 2000 AND msg_from = 1000)
                   )
           ) AS t
        ON t.msg_time = m.msg_time
     WHERE ((m.msg_to = 1000 AND m.msg_from = 2000) OR
            (m.msg_to = 2000 AND m.msg_from = 1000)
           )
    

    如果这些字符之间恰好有两条(或更多条)消息具有相同的最新时间戳,那么它们都会被选中;目前没有在碰撞之间进行选择的依据。如果您认为这是个问题,您可以安排使用上面的查询(作为子查询)查找MAX(msg_id)

    SELECT m2.*
      FROM message AS m2
      JOIN (SELECT MAX(m.msg_id) AS msg_id
              FROM message AS m
              JOIN (SELECT MAX(msg_time) AS msg_time
                      FROM message
                     WHERE ((msg_to = 1000 AND msg_from = 2000) OR
                            (msg_to = 2000 AND msg_from = 1000)
                           )
                   ) AS t
                ON t.msg_time = m.msg_time
             WHERE ((m.msg_to = 1000 AND m.msg_from = 2000) OR
                    (m.msg_to = 2000 AND m.msg_from = 1000)
                   )
           ) AS i
        ON i.msg_id = m2.msg_id
    

    警告代码未经任何 DBMS 正式测试。

    【讨论】:

      【解决方案4】:

      经过一番思考,我想出了这个:

      SELECT min_user AS min(msg_from, msg_to), max_user AS max(msg_from, msg_to), 
      max(msg_date) FROM msg GROUP BY min_user, max_user
      

      我仍然不太确定如何从消息中获取附加数据,但我会考虑一下。

      【讨论】:

      • 这将为一对选择多个值。对是独一无二的。更改对的顺序无关紧要。可以有多个消息。我只需要最新消息。
      • 好的,那么你添加 LIMIT 1
      • 但这不会给我其他独特的配对。我只会得到一对。一个用户接收来自多个用户的消息。我需要来自所有独特对的最新消息。对不依赖于方向。
      猜你喜欢
      • 1970-01-01
      • 2015-10-28
      • 2023-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-23
      • 2020-07-04
      相关资源
      最近更新 更多