Помогите с рекурсивным SELECT - PullRequest
3 голосов
/ 22 декабря 2009

Вот ситуация. У меня есть две таблицы:

  • пользователи (зарегистрированные пользователи веб-сайт),
  • сообщений (личные сообщения, которые они отправляли друг другу)

В таблице сообщений есть следующие столбцы (только важные):

  • id,
  • отправитель (идентификатор пользователя, который отправил сообщение),
  • идентификатор получателя пользователя, которому сообщение было отправлено),
  • reply_to (идентификатор сообщения, для которого это сообщение ответить, может быть NULL)

Что мне нужно сделать, так это создать запрос SELECT, который выберет полный разговор между двумя пользователями. То есть если пользователь A отвечает на сообщение, отправленное пользователем B, а пользователь B отвечает обратно на сообщение, я хотел бы получить три строки, подобные этой:

  • message03: ответ на сообщение02
  • message02: ответ на сообщение01
  • message01 от пользователя A к пользователю B

Я уверен, что такой запрос SELECT можно построить на основе поля reply_to, но я никогда не делал ничего подобного, поэтому мне нужна небольшая помощь.

Запрос SELECT должен быть для базы данных MySQL.

Ответы [ 5 ]

7 голосов
/ 22 декабря 2009

На самом деле вы не правы: с ANSI SQL это не возможно. Некоторые базы данных с расширениями поставщиков (например, Oracle CONNECT BY) могут делать то, что вы хотите, но не просто старый SQL.

Мой совет? Измените свои данные, чтобы включить более простое решение.

В этом случае присвойте каждому сообщению chat_id. Если пользователь публикует новое сообщение, присвойте ему новое (в настоящее время неиспользуемое) значение. Если они ответят, оставьте разговор_ид сообщений, на которые вы отвечаете.

Тогда запрос данных становится тривиальным.

6 голосов
/ 22 декабря 2009

Я бы предложил добавить поле conversation_id в таблицу messages. Каждое новое сообщение без ответа будет иметь сгенерированный conversation_id, и тогда каждый ответ, основанный на этом сообщении, будет использовать тот же идентификатор. Тогда ваш запрос будет простым:

select * from messages where conversation_id = ? order by id asc
2 голосов
/ 22 декабря 2009

Я использую технику, которую я нашел в книге Джо Селко «SQL для умных» - глава 29 о модели вложенных множеств для деревьев. Ведение данных немного уродливо (вставка, обновление, удаление), но выбор выполняется очень быстро. Код в книге очень подробный и хорошо объясненный. Также есть некоторая информация о том, как преобразовать имеющиеся у вас данные в эту новую модель.

2 голосов
/ 22 декабря 2009

Это модель списка смежности.

MySQL не имеет собственного способа запроса, но вы можете использовать определенный хак: создайте функцию, подобную этой:

CREATE FUNCTION hierarchy_connect_by_parent_eq_prior_id(value INT) RETURNS INT
NOT DETERMINISTIC
READS SQL DATA
BEGIN
        DECLARE _id INT;
        DECLARE _parent INT;
        DECLARE _next INT;
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET @id = NULL;

        SET _parent = @id;
        SET _id = -1;

        IF @id IS NULL THEN
                RETURN NULL;
        END IF;

        LOOP
                SELECT  MIN(id)
                INTO    @id
                FROM    messages
                WHERE   reply_to = _parent
                        AND id > _id;
                IF @id IS NOT NULL OR _parent = @start_with THEN
                        SET @level = @level + 1;
                        RETURN @id;
                END IF;
                SET @level := @level - 1;
                SELECT  id, reply_to
                INTO    _id, _parent
                FROM    messages
                WHERE   id = _parent;
        END LOOP;
END

и использовать его в запросе:

SELECT  CONCAT(REPEAT('    ', level - 1), CAST(hi.id AS CHAR)) AS treeitem, parent, level
FROM    (
        SELECT  hierarchy_connect_by_parent_eq_prior_id(id) AS id, @level AS level
        FROM    (
                SELECT  @start_with := 0,
                        @id := @start_with,
                        @level := 0
                ) vars, messages
        WHERE   @id IS NOT NULL
        ) ho
JOIN    messages hi
ON      hi.id = ho.id

См. Эту статью в моем блоге для более подробного объяснения того, как это работает:

Будут выбраны только дочерние элементы исходного сообщения (идентификатор которого должен использоваться для инициализации @start_with).

Этот запрос дополнительно может быть отфильтрован для значений sender_id и receiver_id, чтобы убедиться, что выбраны только сообщения между пользователями.

1 голос
/ 22 декабря 2009

Используя что-то вроде:

SELECT *
FROM [Messages] 
WHERE 
    (
        [Sender] = @Sender 
            AND [Reciever] = @Reciever
    ) OR (
        [Sender] = @Reciever 
            AND [Reciever] = @Sender)

Получит вам всю историю разговоров. Что касается поля reply_to, я бы не стал его использовать, потому что:

А) Из-за этого будет очень сложно получить первое сообщение разговора.

B) Вы можете использовать другие фильтры, такие как дата или ограничение длины истории, чтобы предотвратить полный вывод каждого разговора, который имели пользователи.

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

В конкретном ответе на ваш вопрос будет работать следующий запрос, при условии, что вы можете разрешить выбор первого сообщения в беседе:

SELECT * 
FROM [Messages] 
WHERE 
    (
        (
            [Sender] = @Sender 
                AND [Reciever] = @Reciever
        ) OR (
            [Sender] = @Reciever 
                AND [Reciever] = @Sender)
    )
    AND id >= @FirstMessageId
    AND id < 
        (
            SELECT TOP 1 [id] 
            FROM [Messages] 
            WHERE [id] > @FirstMessageId
                AND [reply_to] IS NULL
                AND  
                    (       
                        (
                            [Sender] = @Sender 
                                AND [Reciever] = @Reciever
                        ) OR (
                            [Sender] = @Reciever 
                                AND [Reciever] = @Sender
                        )
                    )
    )
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...