2014-10-08 86 views
1

我有一個消息系統。如何獲取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 
+2

您能通過遞減在message.id做訂單獲取由該用戶創建的最新的最新ID? – SoftwareCarpenter 2014-10-08 22:23:57

+0

需要添加最大(發送)列列表和其他列 – radar 2014-10-08 22:32:13

回答

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 
0

相同的答案比SoftwareCarpente,只需添加一個ORDER BY遞減的消息ID(或時間戳)並添加一個LIMIT 1,如果你只需要第一行。

+0

這不會工作,將它?我需要所有線程及其各自的最新消息。 – user1405177 2014-10-08 23:35:44

+0

然後加入線程,並按線程ID和消息ID進行排序。 – 2014-10-09 00:00:04

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 從郵件中 其中USER_ID = 42 UNION SELECT DISTINCT的thread_id FROM收件人 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 

看到這是如何一回事呢?您可以使用聚合MAX()和SQL的設置構建(DISTINCT,UNION)功能構建相關項目的列表,然後加入以獲取您想要的列表。

我假設一旦你有一個合適的消息列表,你可以用另外一兩個連接去獲得你需要的內容。

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,等),然後只保留#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 
) 
+0

謝謝你的評論。我一直相信GROUP BY會選擇與GROUP函數匹配的第一行(並且它在我的服務器中的確出現了這種情況),但我現在不知道要依賴它。這是非常好的信息要知道! – user1405177 2014-10-09 00:49:50