Выборка последних сообщений пользователя в MySQL

    Задача получения последнего сообщения от каждого пользователя - частая проблема при работе с чатами или мессенджерами. В этой статье разберём, как составить корректный SQL-запрос для таблиц users и messages, чтобы получить одно последнее сообщение для каждого отправителя.

    Структура базы данных

    Предположим, у вас есть две таблицы:

    • users - содержит поля id, name, password;
    • messages - содержит поля id, from_id, to_id, message.

    Вам нужно получить для каждого пользователя (отправителя) только одно самое свежее сообщение. Рассмотрим несколько способов.

    Способ 1: Подзапрос с MAX(id)

    Самый простой и часто используемый метод - найти максимальный id сообщения для каждого from_id, так как id обычно автоинкрементный и возрастает с каждым новым сообщением.

    SELECT m.*
    FROM messages m
    INNER JOIN (
        SELECT from_id, MAX(id) AS max_id
        FROM messages
        GROUP BY from_id
    ) latest ON m.from_id = latest.from_id AND m.id = latest.max_id;

    Этот запрос группирует сообщения по отправителю, находит максимальный ID в каждой группе и затем присоединяет исходную таблицу, чтобы получить полные данные сообщения.

    Способ 2: Использование подзапроса с корреляцией

    Если в таблице нет автоинкремента или вы хотите работать с датой, можно использовать коррелированный подзапрос. Для примера добавим поле created_at (тип DATETIME).

    SELECT m.*
    FROM messages m
    WHERE m.id = (
        SELECT m2.id
        FROM messages m2
        WHERE m2.from_id = m.from_id
        ORDER BY m2.created_at DESC
        LIMIT 1
    );

    Этот запрос для каждой строки внешней таблицы находит ID последнего сообщения того же отправителя и оставляет только те строки, которые совпадают. Работает медленнее на больших объёмах данных, но даёт гибкость.

    Способ 3: Использование оконных функций (MySQL 8+)

    Если ваша версия MySQL 8.0 или новее, оптимальным решением будет оконная функция ROW_NUMBER().

    SELECT *
    FROM (
        SELECT m.*,
               ROW_NUMBER() OVER (PARTITION BY from_id ORDER BY id DESC) AS rn
        FROM messages m
    ) ranked
    WHERE rn = 1;

    Здесь мы нумеруем сообщения для каждого отправителя в порядке убывания ID, затем оставляем только первую запись (номер 1). Такой подход легко читается и эффективен.

    Выборка с информацией о пользователе

    Чтобы дополнительно получить имя отправителя, присоедините таблицу users:

    SELECT u.name, m.message, m.id
    FROM messages m
    INNER JOIN users u ON m.from_id = u.id
    INNER JOIN (
        SELECT from_id, MAX(id) AS max_id
        FROM messages
        GROUP BY from_id
    ) latest ON m.from_id = latest.from_id AND m.id = latest.max_id;

    Заключение

    Выборка последнего сообщения для каждого пользователя реализуется несколькими способами: через подзапрос с MAX(), коррелированный подзапрос или оконные функции. Выбор зависит от версии MySQL и объёма данных. Для современных СУБД рекомендуется использовать ROW_NUMBER() - это наиболее производительный и понятный вариант.

    Часто задаваемые вопросы