Существует ли более простой способ достижения этого стиля обмена сообщениями пользователей?
Я создал систему обмена сообщениями для пользователей, что позволяет им отправлять сообщение другому пользователю. Если это первый раз, когда они говорили, начинается новый разговор, если не старый разговор продолжается.
Входящие пользователей перечисляют все разговоры, которые пользователь имел со всеми другими пользователями, которые затем упорядочиваются по цепочке, в которой есть последняя запись.
Пользователь может иметь только один разговор с другим пользователем.
Когда пользователь нажимает на одну из этих бесед, они переходят на страницу, показывающую весь разговор, который у них был с самыми новыми сообщениями вверху. Так что это похоже на функциональность чата обмена сообщениями.
У меня есть две таблицы:
- userconversation
- UserMessages
userconversation
Содержит идентификатор автоматического инкремента, который является идентификатором разговора, вместе с userId и friendId.
Тот, кто инициирует первый разговор, всегда будет userId и получателем friendId, это никогда не изменится для этого разговора.
+----+--------+----------+
| id | userId | friendId |
+----+--------+----------+
UserMessages
Содержит конкретные сообщения, а также флаг чтения, время и диалогId
+----+---------+--------+------+------+----------------+
| id | message | userId | read | time | conversationId |
+----+---------+--------+------+------+----------------+
Как это работает
Когда пользователь переходит к сообщению другого пользователя, будет запущен запрос, чтобы проверить, совпадают ли оба пользователя в таблице пользовательского диалога, если используется conversationId
, и разговор продолжается, если не создается новая строка для них с уникальным conversationId
.
Где он усложняется
До сих пор все хорошо, однако когда дело доходит до отображения сообщения "Входящие" всех разговоров, отсортированного по последнему сообщению, сложно сделать один запрос.
Чтобы иметь возможность перечислять разговоры, вы должны сначала найти последнюю запись каждого разговора, но поскольку вы не можете заказать ее перед группой, это невозможно сделать с одним запросом на две таблицы, поэтому я должен использовать следующее:
SELECT
c.id,
c.userId,
c.friendId,
m2.message,
m2.read,
UNIX_TIMESTAMP(m2.time),
user1.username,
user2.username
FROM
(SELECT MAX(m1.id) AS MessageID
FROM usermessages m1
GROUP BY m1.conversationId) latest_msg
INNER JOIN usermessages m2 ON latest_msg.MessageID = m2.id
INNER JOIN userconversation c ON m2.conversationId = c.id
INNER JOIN user user1 ON c.userId = user.id
INNER JOIN user user2 ON c.friendId = user.id
WHERE c.userId = :userId OR c.friendId = :userId
ORDER BY m2.id DESC
LIMIT 10
Я просто не думаю, что это лучший способ, которым это можно сделать, но не может думать о том, как другие методы тоже подходят к нему?
Таблица базы данных - это InnoDB, чтобы ускорить объединение и улучшить целостность данных, поэтому я не могу иметь две строки автоматического увеличения.
Есть ли другой способ избавиться от таблицы userconversation и создать уникальный идентификатор, который будет помещен в столбец talkId? Я мог бы просто переместить userId и friendId на usermessages... но это создаст много избыточных данных?
Ответы
Ответ 1
Хмм, может быть, я неправильно понимаю вашу проблему... но для меня решение довольно просто:
SELECT c.*, MAX(m.time) as latest_post
FROM conversations as c
INNER JOIN messages as m ON c.id = m.conversation_id
WHERE c.userId = 222 OR c.friendId = 222
GROUP BY c.id
ORDER BY latest_post DESC
здесь мои тестовые данные:
Беседы:
id userId friendId
1 222 333
2 222 444
Сообщения:
id message time (Desc) conversation_id
14 rty 2012-05-14 19:59:55 2
13 cvb 2012-05-14 19:59:51 1
12 dfg 2012-05-14 19:59:46 2
11 ert 2012-05-14 19:59:42 1
1 foo 2012-05-14 19:22:57 2
2 bar 2012-05-14 19:22:57 2
3 foo 2012-05-14 19:14:13 1
8 wer 2012-05-13 19:59:37 2
9 sdf 2012-05-13 19:59:24 1
10 xcv 2012-05-11 19:59:32 2
4 bar 2012-05-10 19:58:06 1
6 zxc 2012-05-08 19:59:17 2
5 asd 2012-05-08 19:58:56 1
7 qwe 2012-05-04 19:59:20 1
Результат запроса:
id userId friendId latest_post
2 222 444 2012-05-14 19:59:55
1 222 333 2012-05-14 19:59:51
Если это не так... просто проигнорируйте мой ответ: P
Надеюсь, что это поможет
Ответ 2
Если вы запрашиваете способ сохранить все свои текущие функциональные и рабочие потоки, но сохраните данные в одной таблице, я думаю, что вы довольно близки.
Вместо того, чтобы conversationId
быть ключом к другой таблице, я бы вместо этого указал на идентификатор сообщения, которое начало разговора. Это создало бы отношения между родителями и детьми между сообщениями, которые начали разговор, и всеми теми, которые последовали за ним. Чтобы иметь возможность видеть все разговоры, вы просто выбираете все сообщения, где conversationId
имеет значение null. Ниже представлено сообщение из 2 сообщений:
+----+---------+------+------------------+----------------+--------+----------+
| id | message | read | time | conversationId | toUser | fromUser |
+----+---------+------+------------------+----------------+--------+----------+
| 1 | test 1 | 0 | (some timestamp) | null | 3 | 4 |
| 2 | test 2 | 0 | (some timestamp) | 1 | 4 | 3 |
+----+---------+------+------------------+----------------+--------+----------+
Разговор был инициирован пользователем 3. Все сообщения в разговоре могут быть отфильтрованы на conversationId
. Одно из ограничений этого дизайна заключается в том, что только различие между разговором может быть только у двух пользователей.
Обновить
Вы можете получить последнее сообщение с идентификатором беседы следующим образом:
SELECT id, message
FROM userMessages
WHERE conversationId = {conversationId}
ORDER BY time DESC
LIMIT 1
Ответ 3
Если у вас есть только один разговор между пользователями, я не вижу причины для отдельной таблицы цепочек. Чтобы этот запрос работал быстро, вам понадобится составной индекс на (user, message_id)
, который невозможен, если эти поля находятся в разных таблицах. Переместите user_id
и friend_id
в userconversations
. Это сделает вашу таблицу 8
байтов на запись более тяжелой (даже если предполагается 8
-байтовые идентификаторы), что вряд ли является проблемой для таблицы, содержащей текстовые сообщения.
Если у вас мало разговоров на пользователя с большим количеством сообщений в каждом, используйте это:
SELECT um.*
FROM (
(
SELECT MAX(id) AS messageId
FROM usermessages m1
WHERE user_id = :me
GROUP BY
friend_id
ORDER BY
messageId DESC
LIMIT 10
)
UNION ALL
(
SELECT MAX(id) AS messageId
FROM usermessages m1
WHERE frient_id = :me
GROUP BY
user_id
ORDER BY
messageId DESC
LIMIT 10
)
) q
JOIN usermessages um
ON um.id = q.messageId
ORDER BY
id DESC
LIMIT 10
Создайте отдельные индексы на user_id
и friend_id
Если у вас много разговоров с несколькими сообщениями в каждом, используйте этот запрос:
(
SELECT *
FROM usermessages um
WHERE user_id = :me
AND id =
(
SELECT MAX(id)
FROM usermessages umi
WHERE umi.user_id = um.user_id
AND umi.friend_id = um.friend_id
)
ORDER BY
id DESC
LIMIT 10
)
UNION ALL
(
SELECT *
FROM usermessages um
WHERE frient_id = :me
AND id =
(
SELECT MAX(id)
FROM usermessages umi
WHERE umi.user_id = um.user_id
AND umi.friend_id = um.friend_id
)
ORDER BY
id DESC
LIMIT 10
)
ORDER BY
id DESC
LIMIT 10
Идея этого запроса заключается в том, что он просто сбрасывает все сообщения для данного пользователя, проверяя, что каждое сообщение является последним в его разговоре. Это может быть намного быстрее, чем сортировка всех последних сообщений для всех разговоров (если у вас их много).
Чтобы это быстро работало, создайте индексы на
friend_id
user_id, friend_id
Ответ 4
Если вы хотите упростить свой запрос, вы должны добавить последний идентификатор сообщения в свою таблицу userconversation
:
ALTER TABLE userconversation ADD lastusermessageid
то каждый раз, когда вы добавляете новое сообщение, вы должны обновить свою дискуссию пользователя:
INSERT INTO userconversation(userId, friendId, lastusermessageid)
VALUES (:userId, :friendId, :lastusermessageid)
ON DUPLICATE KEY UPDATE lastusermessageid = VALUES(lastusermessageid)
и, наконец, добавить индексы для всех внешних ключей:
SELECT
c.id,
c.userId,
c.friendId,
m.message,
m.read,
UNIX_TIMESTAMP(m.time),
user1.username,
user2.username
FROM
userconversation c
INNER JOIN usermessages m ON c.lastusermessageid = m.id
INNER JOIN user user1 ON c.userId = user.id
INNER JOIN user user2 ON c.friendId = user.id
WHERE
c.userId = :userId OR c.friendId = :userId
ORDER BY
m.id DESC
LIMIT 10
Ответ 5
Поскольку у данной пары пользователей может быть не более одного разговора, нет необходимости "изобретать" отдельный ключ, чтобы идентифицировать разговоры. Кроме того, формулировка вашего вопроса, похоже, предполагает, что сообщение всегда отправляется одному пользователю, поэтому я, вероятно, займусь чем-то вроде этого:
![enter image description here]()
Теперь о данной модели есть несколько вещей:
- Предполагается, что сообщения между теми же двумя пользователями не могут генерироваться чаще, чем разрешение, предоставляемое типом, используемым для SEND_TIME. 1
- Направление сообщения не определяется по заказу USER1_ID и USER2_ID, но с отдельным флагом (DIRECTION). Таким образом, сообщение между заданными пользователями всегда будет иметь одинаковую комбинацию USER1_ID и USER2_ID (в соответствии с приведенным выше CHECK), независимо от того, кто отправил и кто получил сообщение. Это значительно упрощает запрос.
- Прискорбно, что все таблицы InnoDB кластеризуются, поэтому вторичный индекс
I1
относительно дорого. Есть способы обойти это, но возникающие осложнения, вероятно, не стоят того.
В этой модели данных довольно легко сортировать "беседы" (идентифицированные парами пользователей) последним сообщением. Например (замените 1
на желаемый пользовательский USER_ID):
SELECT *
FROM (
SELECT USER1_ID, USER2_ID, MAX(SEND_TIME) NEWEST
FROM MESSAGE
WHERE (USER1_ID = 1 OR USER2_ID = 1)
GROUP BY USER1_ID, USER2_ID
) Q
ORDER BY NEWEST DESC;
(OR USER2_ID = 1
является причиной вторичного индекса I1
.)
Если вы хотите не только последние времена, но и последние сообщения, вы можете сделать что-то вроде этого:
SELECT * FROM MESSAGE T1
WHERE
(USER1_ID = 1 OR USER2_ID = 1)
AND SEND_TIME = (
SELECT MAX(SEND_TIME)
FROM MESSAGE T2
WHERE
T1.USER1_ID = T2.USER1_ID
AND T1.USER2_ID = T2.USER2_ID
)
ORDER BY SEND_TIME DESC;
Вы можете играть с ним в SQL Fiddle.
1 Если это не так, вы можете использовать монотонный инкрементирующий INT вместо этого, но вам придется SELECT MAX(...)
самостоятельно, так как автоинкремент не работает с подмножеством PK; или просто сделать его одним PK и иметь вторичные индексы как для USER1_ID, так и для USER2_ID (к счастью, они будут более тонкими, поскольку ПК будет более тонким).
Ответ 6
Как создать быструю Facebook-подобную систему сообщений.
проверены и широко используются пользователями Arutz Sheva - http://www.inn.co.il (иврит).
-
создать таблицу "topic" (беседа):
CREATE TABLE pb_topics
(
t_id
int(11) NOT NULL AUTO_INCREMENT,
t_last
int(11) NOT NULL DEFAULT '0',
t_user
int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (t_id
),
KEY last
(t_last
)
) ENGINE=InnoDB AUTO_INCREMENT=137106342 DEFAULT CHARSET=utf8
-
создать связь между пользователем и беседой:
CREATE TABLE pb_links
(
l_id
int(11) NOT NULL AUTO_INCREMENT,
l_user
int(11) NOT NULL DEFAULT '0',
l_new
int(11) NOT NULL DEFAULT '0',
l_topic
int(11) NOT NULL DEFAULT '0',
l_visible
int(11) NOT NULL DEFAULT '1',
l_bcc
int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (l_id
) USING BTREE,
UNIQUE KEY topic-user
(l_topic
,l_user
),
KEY user-topicnew
(l_user
,l_new
,l_topic
) USING BTREE,
KEY user-topic
(l_user
,l_visible
,l_topic
) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=64750078 DEFAULT CHARSET=utf8
-
создать сообщение
CREATE TABLE pb_messages
(
m_id
int(11) NOT NULL AUTO_INCREMENT,
m_from
int(11) NOT NULL,
m_date
datetime NOT NULL DEFAULT '1987-11-13 00:00:00',
m_title
varchar(75) NOT NULL,
m_content
mediumtext NOT NULL,
m_topic
int(11) NOT NULL,
PRIMARY KEY (m_id
),
KEY date_topic
(m_date
,m_topic
),
KEY topic_date_from
(m_topic
,m_date
,m_from
)
) ENGINE=InnoDB
Разговор может состоять из двух или более друзей (BCC был добавлен как электронная почта, но вы можете пропустить его).
Вставьте новое сообщение:
1. Создать новую тему
2. Создание ссылок для пользователей (от/до)
3. Добавить сообщение
(4. Обновить таблицу кэша пользователей - у пользователя есть сообщения)
Добавить сообщение в тему:
Добавить сообщение
Выберите папку:
select
z.*, group_concat(u_name) as users_name from
(select max(m_id) as m_id, m_topic as t_id, m_From, m_title,m_date, l_new
from pb_links as l1, pb_messages
where l1.l_user=<user> and m_from < If(inbox, "<>", "=") > and m_topic=l_topic and l1.l_visible=1
group by m_topic order by m_id desc limit " & iPage * 35 & ",35) z
left join pb_links l2 on (l2.l_topic=t_id)
left join users on (l_user=u_id and l_bcc=0 and l_user<user>)
group by l_topic order by m_date desc;
Подробнее:
Первый - это внутренний выбор - это самый быстрый способ (я проверил около 7 других опций, также проверял в версиях Percona/MariaDB), чтобы получить все сообщения и получить также последнее сообщение для отображения в списке.
Также смотрите во внутреннем IF - в папке "Входящие", последнее сообщение - это кто-то, но не я, а In outbox - наоборот. LIMIT используется для подкачки.
Внешний, используемый для добавления списка пользователей (просто имя имени запятой) и дополнительной информации для только одного сообщения для каждой темы и после подкачки (мне нужно добавить список пользователей только для 35-страничных сообщений, а не для всей моей большой истории).
Кроме того, я написал на иврите здесь:
http://blogs.microsoft.co.il/blogs/moshel/archive/2010/08/12/quot-x-quot.aspx
для создания простой таблицы кеша и запретить рабочую нагрузку для подсчета количества из таблицы занятости.
Ответ 7
Почему вы разбиваете данные на разговоры?
Если бы это был я, я бы использовал одну таблицу под названием "usermessages" со следующим форматом:
+----+--------+----------+-------------+------------+--------+
| id | userto | userfrom | timecreated | timeviewed | message|
+----+--------+----------+-------------+------------+--------+
Разговор идентифицируется комбинацией столбцов "userto" и "user from". Итак, когда вы хотите выбрать весь разговор:
SELECT * FROM usermessages
WHERE (userto = :userto OR userto = :userfrom)
AND (userfrom = :userfrom OR userfrom = :userto)
ORDER BY timecreated DESC
LIMIT 10
Ответ 8
он используется на fiverr.com и www.infinitbin.com. Я разработал бесконечность. Он имеет две базы данных, такие как ваши. таблица входящих сообщений: -
+----+--------+----------+-------------+------------+--------------------------------+
| id | useridto | useridfrom | conversation | last_content | lastviewed | datecreated|
+----+--------+----------+-------------+------------+--------------------------------+
Эта таблица очень важна, используется для перечисления цепочек/входящих. Поле last_content содержит 140 символов из последнего сообщения между цепочками. lastviewed - целое поле, пользователь, который продолжает отправлять сообщение, является последним просмотренным, если другой пользователь в разговоре читает сообщение. он обновляется до NULL. Поэтому для получения уведомлений вы для lastviewed, который не является нулевым, а не зарегистрированным идентификатором пользователя.
Поле беседы является "userid-userid", поэтому строки. Чтобы проверить, начали ли пользователи разговор, вы объединяете там user_ids с дефисом и проверяете его.
Такая система обмена сообщениями очень усложняет ее.
Вторая таблица довольно проста.
+----+--------+----------+-------------+-------+
| id | inboxid | userid | content | datecreated|
+----+--------+----------+-------------+-------+
Ответ 9
Я не тестировал этот подход, так как сейчас у меня нет доступа к mysqldb. Но, я думаю, вы должны это сделать, используя функцию ранжирования. Поскольку mysql не имеет эквивалента функции row_number для Oracle, я думаю, вы можете сделать это следующим образом:
Select * from (
Select
uc.id,
uc.user_id,
uc.friend_id
um.message
um.read,
um.time,
@rownum := IF(@prev_val = um.conversation_id, @rownum + 1, 1) AS rank,
@prev_val := um.conversation_id
From
userconversation uc,
usermessages um,
(select @row_num:=1) rows,
(select @prev_val:='') partitions
Where
uc.id=um.conversation_id
and c.userId = 222 OR c.friendId = 222
Order By
um.conversation_id,um.id desc
)t where t.rank=1
Ответ 10
Я бы установил его таким образом
Данные таблицы
conversations (#id, last_message_id)
participation (#uid1, #uid2, conversation_id)
messages (#conversation_id, #id, uid, contents, read, *time)
разговоры
Эта таблица будет использоваться в основном для генерации нового идентификатора для каждой беседы вместе с вычисленным полем последнего обновления (для оптимизации). Эти два пользователя были отключены от этой таблицы и переведены в participation
.
Участие
В этой таблице записаны разговоры между двумя пользователями в обоих направлениях; чтобы объяснить, почему, взгляните на следующий ключ:
ALTER TABLE `table` ADD PRIMARY(uid1, uid2);
Хотя это хорошо для обеспечения уникальности и простого поиска, вы должны знать следующее поведение:
-
SELECT * FROM table WHERE uid1=1 AND uid2=2
-
SELECT * FROM table WHERE uid1=1
-
SELECT * FROM table WHERE uid1=1 AND uid2>5
-
SELECT * FROM table WHERE uid2=2
Первые два запроса выполняются очень хорошо, MySQL также оптимизирует поиск идентичности в первой части вашего ключа. Третья также дает неплохую производительность, так как вторая часть вашего ключа может использоваться для запросов диапазона. Последний запрос не работает хорошо, потому что индекс "левый" и, следовательно, выполняет полное сканирование таблицы.
сообщения
В этой таблице хранятся фактические отправленные сообщения, содержащие идентификатор разговора, идентификатор отправителя, содержимое, флаг чтения и время его отправки.
Операция
отправка сообщений
Чтобы определить, был ли разговор между двумя пользователями уже установлен, вы можете просто запросить таблицу participation
:
SELECT conversation_id FROM participation WHERE uid1=:sender_id AND uid2=:receiver_id
Если он еще не существует, вы создаете обе записи:
INSERT INTO conversations (last_message_id) VALUES (NULL);
# fetch last insert id here
INSERT INTO participation VALUES (:sender_id, :receiver_id, :conversation_id), (:receiver_id, :sender_id, :conversation_id);
INSERT INTO messages VALUES (:conversation_id, 0, :sender_id, :message_contents, 0, NOW());
UPDATE conversations SET last_message_id=LAST_INSERT_ID() WHERE id = :conversation_id
Если разговор уже настроен: INSERT INTO messages VALUES (: convers_id, 0,: sender_id,: message_contents, 0, NOW()); Переговоры UPDATE SET last_message_id = LAST_INSERT_ID() WHERE id =: convers_id
Примечание. Оператор UPDATE можно планировать как LOW_PRIORITY, потому что вы не всегда должны быть на 100% правильнее.
обзор беседы
Это стало более простым запросом:
SELECT other_user.name, m.contents, m.read, c.id
FROM participation AS p
INNER JOIN user AS other_user ON other_user.id = p.uid2
INNER JOIN conversation AS c ON c.id = p.conversation_id
INNER JOIN messages AS m ON m.id = c.last_message_id
WHERE p.uid1 = :user_id
ORDER BY m.time DESC
LIMIT 50
Отказ от ответственности: я не проверял это, но запись должна иметь смысл для вас.
Оптимизация
Другая причина, по которой хорошо иметь двухстороннюю таблицу, заключается в том, что она подготовлена для очертания, метод, в котором вы перемещаете связанные данные в другую базу данных (на другой машине); на основе определенных правил вы определяете, откуда взять информацию.
Вы можете перемещать данные следующими способами:
- разделите таблицу
participation
на основе поля uid1
- разделите таблицу
messages
на основе поля conversation_id
Обзор сообщений будет более сложным, так как вам, вероятно, придется делать два запроса; это может быть несколько смягчено кэшами (и в крайних случаях документами).
Надеюсь, это даст вам некоторые идеи относительно будущего планирования:)
Ответ 11
Думаю, вам не нужно создавать таблицу пользовательской беседы.
Если только пользователь может иметь только один разговор с кем-то, уникальный идентификатор для этого потока - это concat между userId и friendId. Поэтому я перемещаю столбец friendId в таблице usersmessage. Проблема порядка (friendId-userId - это тот же поток userId-friendId) может быть решена так:
SELECT CONCAT(GREATEST(userId,FriendId),"_",LEAST(userId,FriendId)) AS threadId
Теперь возникает проблема извлечения последнего сообщения после потока GROUP BY threadId.
Я думаю, что это хорошее решение, сделать concat между DATE и сообщением и после MAX в этом поле.
Я предполагаю, что для простоты дата столбца поле DATETIME ('YYYY-mm-dd H: i: s'), но это не нужно, потому что есть функция FROM_UNIXTIME.
Итак, окончательный запрос
SELECT
CONCAT(GREATEST(userId,FriendId),"_",LEAST(userId,FriendId)) AS threadId,
friendId, MAX(date) AS last_date,
MAX(CONCAT(date,"|",message)) AS last_date_and_message
FROM usermessages
WHERE userId = :userId OR friendId = :userId
GROUP BY threadId ORDER BY last_date DESC
результат поля last_date_and_message выглядит примерно так:
2012-05-18 00:18:54|Hi my friend this is my last message
он может быть просто проанализирован с вашего кода на стороне сервера.
Ответ 12
Расширение ответа, предложенного Наблюдателем.
Вам следует рассмотреть возможность отмены концепции "беседы" для дальнейшего упрощения.
+----+---------+------+------------------+--------+----------+
| id | message | read | time | toUser | fromUser |
+----+---------+------+------------------+--------+----------+
| 1 | test 1 | 0 | (some timestamp) | 3 | 4 |
| 2 | test 2 | 0 | (some timestamp) | 4 | 3 |
+----+---------+------+------------------+--------+----------+
Список всех разговоров для пользователя 123:
SELECT * FROM (
SELECT id, message, toUser, fromUser
FROM userMessages
WHERE toUser = 123 OR fromUser = 123
ORDER BY id DESC
) AS internalTable
GROUP BY toUser, fromUser
Перечислить весь разговор между пользователем 123 и пользователем 456:
SELECT *
FROM userMessages
WHERE (toUser = 123 OR fromUser = 123)
AND (toUser = 456 OR fromUser = 456)
ORDER BY time DESC
Ответ 13
Простейший способ, я думаю, для этого:
Таблицы:
conversation(cid | userId | friendId | last_message_id)
messages(mid | message | userId | read | time | cid)
Затем обновите last_message_id после того, как каждое сообщение будет вставляться пользователями в разговорном порядке.
И затем запустите этот простой запрос. Это даст вам то, что вы хотите.
SELECT * FROM conversation c, messages m
WHERE (c.userId='$uid' OR c.friendId='$uid')
AND c.last_msg_id=m.message_id
ORDER BY created_time DESC
$uid - это идентификатор зарегистрированного пользователя.
Итак, в самом деле, что делает этот процесс:
- Отображение всех разговоров зарегистрированного пользователя.
- Ссылка на последнее сообщение (так что вам не нужна группа)
- И последнее отображение сообщений по порядку.