帮助递归SELECT

时间:2009-12-22 15:32:34

标签: sql mysql

这是情况。我有两张桌子:

  • 用户(注册用户) 网站),
  • 消息(彼此之间发送的个人消息)

消息表包含这些列(只是重要的列):

  • id,
  • 发件人(发送的用户的ID) 消息),
  • 用户的接收者ID 消息已发送),
  • reply_to(此消息所在的消息的ID 回复,可以是NULL)

我需要做的是构建一个SELECT查询,它将选择2个用户之间的完整对话。即如果用户A回复用户B发送的消息而用户B回复该消息,我想得到三行:

  • message03:回复message02
  • message02:回复message01
  • message01从用户A到用户B

我确信可以根据reply_to字段构造这样的SELECT查询,但我之前从未做过类似的事情所以我需要一些帮助。

SELECT查询应该是针对MySQL数据库的。

5 个答案:

答案 0 :(得分:7)

实际上你是不对的:使用ANSI SQL,不可能。某些具有供应商扩展的数据库(例如Oracle的CONNECT BY)可能能够执行您想要的操作,但不能使用普通的旧SQL。

我的建议?更改您的数据,以便提供更简单的解决方案。

在这种情况下,给每条消息一个conversation_id。如果用户发布了新消息,请为其指定一个新的(当前未使用的)值。如果他们回复,请保留要回复的邮件的conversation_id。

然后查询数据变得微不足道。

答案 1 :(得分:6)

我建议在conversation_id表中添加messages字段。每个新的非回复消息都将生成conversation_id,然后基于该消息的每个回复将使用相同的ID。然后你的查询很简单:

select * from messages where conversation_id = ? order by id asc

答案 2 :(得分:2)

这是邻接列表模型。

MySQL没有本地方式来查询它,但你可以使用某个hack:创建一个这样的函数:

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

在我的博客中查看此文章,了解有关其工作原理的更详细说明:

只会选择原始邮件的子邮件(其id应用于初始化@start_with)。

此查询还可以针对sender_idreceiver_id的值进行过滤,以确保仅选择用户之间的消息。

答案 3 :(得分:2)

我使用了我在Joe Celko的SQL for Smarties书中找到的一种技术 - 第29章关于树的嵌套集模型。维护数据有点难看(插入,更新,删除),但选择速度非常快。书中的代码非常详尽,并且有很好的解释。还有一些关于如何将您拥有的数据转换为这个新模型的信息。

答案 4 :(得分:1)

使用类似的东西:

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

将为您提供整个对话历史记录。至于reply_to字段,我不会使用它,因为:

A)检索会话的第一条消息会非常复杂。

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
                        )
                    )
    )