如何获取SQL中的最新行?

时间:2014-10-08 22:17:37

标签: mysql sql

我有一个消息系统。

threads
+----+-------+
| id | title |
+----+-------+
| PK | TEXT  |
+----+-------+

messages
+----+--------------+----------------+-----------+-------------+---------+
| id |   from_id    |   thread_id    |   sent    |   parent    | message |
+----+--------------+----------------+-----------+-------------+---------+
| PK | FK(users.id) | FK(threads.id) | TIMESTAMP | messages.id | TEXT    |
+----+--------------+----------------+-----------+-------------+---------+

recipients
+----+-----------------+--------------+--------+
| id |     msg_id      |    to_id     | status |
+----+-----------------+--------------+--------+
| PK | FK(messages.id) | FK(users.id) | ENUM   |
+----+-----------------+--------------+--------+

users
+----+---------+
| id |  name   |
+----+---------+
| PK | VARCHAR |
+----+---------+

基本上,它是一个消息系统,其中包括:

  • 消息线程可以有多个收件人(收件人表)
  • 每个消息线程都有一个标题(threads.title)
  • 每个用户都有自己的状态(读取,隐藏,未读)每封邮件(recipients.status)
  • 可以回复每条消息(messages.parent指向另一个messages.id)

所以希望我的架构是正确的。

我希望获得所有线程的列表,其中显示了线程中最新的消息,以及该消息的作者:

+----------+------------+-----------+--------------+------------------+---------------+-------------------+
| users.id | users.name | thread.id | thread.title | messages.message | messages.sent | recipients.status |
+----------+------------+-----------+--------------+------------------+---------------+-------------------+

问题是将最新消息作为查询的一部分。鉴于recipients.status = 1意味着未读..暂时忽略用户(这是一个相对简单的连接到其余表...),也假设我们想要用户1的线程:

SELECT threads.id, title, message, sent, recipients.status
FROM recipients
JOIN messages
ON messages.id=recipients.msg_id
JOIN threads ON threads.id=messages.thread_id
WHERE recipients.to_id=1
AND recipients.status=1

这可以获取用户参与的所有线程中的所有消息。但是,我只需要最新的消息,这就是我被困住的地方。

我不喜欢的一种解决方案(有没有理由不这样做?)

SELECT *
FROM (
    SELECT threads.id, title, message, sent, recipients.status
    FROM recipients
    JOIN messages
    ON messages.id=recipients.msg_id
    JOIN threads ON threads.id=messages.thread_id
    WHERE recipients.to_id=1
    AND recipients.status=1
    ORDER BY sent DESC
) a
GROUP BY id

4 个答案:

答案 0 :(得分:1)

  

我非常不喜欢的一种解决方案(有什么理由不这样做   这样做?)

您的查询不一定会为每个线程选择具有最新sent值的行。即使您的内部查询按sent DESC排序,mysql也可以自由选择每个组中的任何值:

https://dev.mysql.com/doc/refman/5.0/en/group-by-extensions.html

  

MySQL扩展了GROUP BY的使用,以便选择列表可以引用   未在GROUP BY子句中命名的非聚合列。这意味着   前面的查询在MySQL中是合法的。您可以使用此功能   通过避免不必要的列排序来获得更好的性能   分组。但是,这主要适用于每个中的所有值   GROUP BY中未命名的非聚合列对于每个列都是相同的   组。服务器可以自由选择每个组中的任何值,所以   除非它们相同,否则所选择的值是不确定的。   此外,不能从每个组中选择值   受添加ORDER BY子句的影响。对结果集进行排序   选择值后发生,ORDER BY不影响   服务器选择的每个组中的值。

我建议使用变量模拟row_number()按照发送时间顺序对线程内的消息进行编号(即线程中最近发送的消息将是#1,最近的第2个#2等)和然后只保留#1消息。

SELECT * FROM (
    SELECT threads.id, title, message, sent, recipients.status,
    @rowNumber := IF(@prevId = threads.id,@rowNumber+1,1) rowNumber,
    @prevId := threads.id
    FROM recipients
    JOIN messages
    ON messages.id=recipients.msg_id
    JOIN threads ON threads.id=messages.thread_id
    WHERE recipients.to_id=1
    AND recipients.status=1
    ORDER BY threads.id, sent DESC
) t1 WHERE rowNumber = 1

修改

使用not exists仅选择不存在同一线程中更新消息的消息的另一种方法。

SELECT threads.id, title, message, sent, recipients.status
FROM recipients
JOIN messages
ON messages.id=recipients.msg_id
JOIN threads ON threads.id=messages.thread_id
WHERE recipients.to_id=1
AND recipients.status=1
AND NOT EXISTS (
    SELECT 1 FROM threads t2
    WHERE t2.id = threads.id
    AND t2.sent > threads.sent
)

答案 1 :(得分:0)

与SoftwareCarpente相同的答案,只需在消息ID(或时间戳)上添加Order By desc,如果只需要第1行,则添加LIMIT 1。

答案 2 :(得分:0)

可以获得每个线程的最新消息ID。

SELECT MAX(id) AS most_recent_message_id,
       thread_id
  FROM messages
 GROUP BY thread_id

你想要这个效率吗?在这种情况下,在(thread_id, id)上创建一个复合索引。

如果您想要一个给定用户(比如用户42)作为发起人或收件人参与的线程列表,则需要使用UNION运算符

SELECT DISTINCT thread_id      来自消息     WHERE user_id = 42    联盟    SELECT DISTINCT thread_id      来自收件人      JOIN消息ON recipients.msg_id = messages.id     WHERE recipients.to_id = 42

这可以获取用户参与的主题。

因此,如果您想要用户所参与的最新消息的ID(作为发起者或收件人),则加入这两个子查询

SELECT most_recent_message_id
  FROM (
        SELECT MAX(id) AS most_recent_message_id,
               thread_id
          FROM messages
         GROUP BY thread_id
       ) AS a
  JOIN (
       SELECT DISTINCT thread_id
         FROM messages
        WHERE user_id = 42
       UNION
       SELECT DISTINCT thread_id
         FROM recipients
         JOIN messages ON recipients.msg_id = messages.id
        WHERE recipients.to_id = 42
       ) AS b ON a.thread_id = b.thread_id 

看看这是怎么回事?您使用SQL的聚合MAX()和set-construction(DISTINCTUNION)功能来构建相关项的列表,然后您加入以获取所需的列表。

我假设一旦你有了一个合适的消息列表,你就可以通过另外一两个连接获得你需要的内容。

答案 3 :(得分:0)

我能够用这个完成它:

SELECT threads.id AS thread_id, threads.title, users.id AS user_id, users.name, m1.message, m1.sent
FROM messages m1
LEFT JOIN messages m2
ON m1.thread_id = m2.thread_id AND m1.sent < m2.sent
JOIN recipients
ON recipients.status=1
AND recipients.msg_id=m1.id
AND recipients.to_id=1
JOIN threads
ON threads.id=m1.thread_id
JOIN users
ON m1.from_id=users.id
WHERE m2.sent IS NULL

问题的相关部分是:

SELECT ...
FROM messages m1
LEFT JOIN messages m2
ON m1.thread_id = m2.thread_id AND m1.sent < m2.sent
WHERE m2.sent IS NULL