我有一个消息系统。
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 |
+----+---------+
基本上,它是一个消息系统,其中包括:
所以希望我的架构是正确的。
我希望获得所有线程的列表,其中显示了线程中最新的消息,以及该消息的作者:
+----------+------------+-----------+--------------+------------------+---------------+-------------------+
| 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
答案 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(DISTINCT
,UNION
)功能来构建相关项的列表,然后您加入以获取所需的列表。
我假设一旦你有了一个合适的消息列表,你就可以通过另外一两个连接获得你需要的内容。
答案 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