【问题标题】:How to implement Twitter retweet action in my database如何在我的数据库中实现 Twitter 转发操作
【发布时间】:2012-07-30 07:49:24
【问题描述】:

我正在实现类似于 Twitter 的 Web 应用程序。我需要实现“转发”操作,一个推文可以被一个人转发多次

我有一个基本的“推文”表,其中包含以下列:

推文: tweet_id |推文文本 | tweet_date_created | tweet_user_id

(其中tweet_id 是推文的主键,tweet_text 包含推文文本,tweet_date_created 是创建推文时的日期时间,tweet_user_idusers 表的外键并标识已创建的用户推文)

现在我想知道如何在我的数据库中实现转发操作。

选项 1

我是否应该创建新的连接表,如下所示:

转推:tweet_id |用户 ID |转推_日期_转推

(其中tweet_idtweets 表的外键,user_idusers 表的外键并标识已转发推文的用户,retweet_date_retweeted 是指定何时转发推文的日期时间完成了。)

优点:不会有空列,当用户进程重新发送时,retweets 表中的新行将被创建。

缺点:查询过程会比较困难,需要连接两个表,并以某种方式按两个日期对推文进行排序(当推文不转发时,按tweet_date_created排序,当推文转发时, 按 retweet_date_retweeted 排序)。

选项 2

或者我应该在tweets 表中将它实现为parent_id,它会看起来像这样:

推文: tweet_id |推文文本 | tweet_date_created | tweet_user_id | parent_id

(所有列保持不变,parent_id 是同一个 tweets 表的外键。创建推文时,parent_id 保持为空。转发推文时,parent_id 包含原始推文 ID ,tweet_user_id 包含处理转推操作的用户,tweet_date_created 包含转推完成时的日期时间,tweet_text 保持为空 - 因为 我们不会让用户在转推时更改原始推文。 )

优点:查询过程更加优雅,因为我不必连接两个表。

缺点: 每次转发推文时都会出现空单元格。因此,如果我的数据库中有 1000 条推文,并且每条推文都被转发 5 次,那么我的 tweets 表中将有 5000 行。


哪种方法最有效?是空单元格更好还是查询过程更干净?

【问题讨论】:

    标签: database database-design relational-database


    【解决方案1】:

    IMO 选项 #1 会更好。加入推文和转推表的查询一点也不复杂,可以通过左连接或内连接来完成,具体取决于您是要显示所有推文还是只显示被转推的推文。并且连接查询应该是高性能的,因为表很窄,被连接的列是整数,并且由于 FK 约束,它们每个都有索引。

    另一个建议是不要用 tweet 或 retweet 标记所有列,这些可以从存储数据的表中推断出来,例如:

    tweet
        id
        user_id
        text
        created_at
    
    retweet
        tweet_id
        user_id
        created_at
    

    和示例连接:

    # Return all tweets which have been retweeted
    SELECT
        count(*),
        t.id
    FROM
        tweet AS t
    INNER JOIN retweet AS rt ON rt.tweet_id = t.id
    GROUP BY
        t.id
    
    # Return tweet and possible retweet data for a specific tweet
    SELECT
        t.id
    FROM
        tweet AS t
    LEFT OUTER JOIN retweet AS rt ON rt.tweet_id = t.id
    WHERE
        t.id = :tweetId
    

    -- 根据请求更新--

    以下内容仅供参考,代表我选择选项 #1 的原因,没有外键也没有任何索引,您必须自己添加这些。但结果应该表明连接不会太痛苦。

    CREATE TABLE `tweet` (
        `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
        `user_id` int(10) unsigned NOT NULL,
        `value` varchar(255) NOT NULL,
        `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`)
    ) ENGINE=MyISAM AUTO_INCREMENT=8 DEFAULT CHARSET=utf8
    
    CREATE TABLE `retweet` (
        `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
        `tweet_id` int(10) unsigned NOT NULL,
        `user_id` int(10) unsigned NOT NULL,
        `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`)
    ) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
    
    # Sample Rows
    
    mysql> select * from tweet;
    +----+---------+----------------+---------------------+
    | id | user_id | value          | created_at          |
    +----+---------+----------------+---------------------+
    |  1 |       1 | User1 | Tweet1 | 2012-07-27 00:04:30 |
    |  2 |       1 | User1 | Tweet2 | 2012-07-27 00:04:35 |
    |  3 |       2 | User2 | Tweet1 | 2012-07-27 00:04:47 |
    |  4 |       3 | User3 | Tweet1 | 2012-07-27 00:04:58 |
    |  5 |       1 | User1 | Tweet3 | 2012-07-27 00:06:47 |
    |  6 |       1 | User1 | Tweet4 | 2012-07-27 00:06:50 |
    |  7 |       1 | User1 | Tweet5 | 2012-07-27 00:06:54 |
    +----+---------+----------------+---------------------+
    
    mysql> select * from retweet;
    +----+----------+---------+---------------------+
    | id | tweet_id | user_id | created_at          |
    +----+----------+---------+---------------------+
    |  1 |        4 |       1 | 2012-07-27 00:06:37 |
    |  2 |        3 |       1 | 2012-07-27 00:07:11 |
    +----+----------+---------+---------------------+
    
    # Query to pull all tweets for user_id = 1, including retweets and order from newest to oldest
    
    select * from (
        select t.* from tweet as t where user_id = 1
        union
        select t.* from tweet as t where t.id in (select tweet_id from retweet where user_id = 1))
    a order by created_at desc;
    
    mysql> select * from (select t.* from tweet as t where user_id = 1 union select t.* from tweet as t where t.id in (select tweet_id from retweet where user_id = 1)) a order by created_at desc;
    +----+---------+----------------+---------------------+
    | id | user_id | value          | created_at          |
    +----+---------+----------------+---------------------+
    |  7 |       1 | User1 | Tweet5 | 2012-07-27 00:06:54 |
    |  6 |       1 | User1 | Tweet4 | 2012-07-27 00:06:50 |
    |  5 |       1 | User1 | Tweet3 | 2012-07-27 00:06:47 |
    |  4 |       3 | User3 | Tweet1 | 2012-07-27 00:04:58 |
    |  3 |       2 | User2 | Tweet1 | 2012-07-27 00:04:47 |
    |  2 |       1 | User1 | Tweet2 | 2012-07-27 00:04:35 |
    |  1 |       1 | User1 | Tweet1 | 2012-07-27 00:04:30 |
    +----+---------+----------------+---------------------+
    

    请注意,在最后一组结果中,我们还能够包含转推并在 #3 转推之前显示 #4 的转推。

    -- 更新--

    您可以通过稍微更改查询来完成您的要求:

    select * from (
        select t.id, t.value, t.created_at from tweet as t where user_id = 1
        union
        select t.id, t.value, rt.created_at from tweet as t inner join retweet as rt on rt.tweet_id = t.id where rt.user_id = 1)
    a order by created_at desc;
    
    mysql> select * from (select t.id, t.value, t.created_at from tweet as t where user_id = 1 union select t.id, t.value, rt.created_at from tweet as t inner join retweet as rt on rt.tweet_id = t.id where rt.user_id = 1) a order by created_at desc;
    +----+----------------+---------------------+
    | id | value          | created_at          |
    +----+----------------+---------------------+
    |  3 | User2 | Tweet1 | 2012-07-27 00:07:11 |
    |  7 | User1 | Tweet5 | 2012-07-27 00:06:54 |
    |  6 | User1 | Tweet4 | 2012-07-27 00:06:50 |
    |  5 | User1 | Tweet3 | 2012-07-27 00:06:47 |
    |  4 | User3 | Tweet1 | 2012-07-27 00:06:37 |
    |  2 | User1 | Tweet2 | 2012-07-27 00:04:35 |
    |  1 | User1 | Tweet1 | 2012-07-27 00:04:30 |
    +----+----------------+---------------------+
    

    【讨论】:

    • 感谢您的回答!但我仍然不确定,哪个选项更好。您能否添加更多参数,为什么您会选择选项#1?这仅仅是因为在这种情况下加入过程既快速又简单?
    • 差不多,另外,正如您所提到的,如果您选择选项#2,您不会在转推表中出现“无用”行,因为您必须输入实际上不是的行转发,但所有转发行都将引用的“父”推文。
    • @MikePurcell 您能否扩展您的答案以包括查询以选择实际的推文提要?我的意思是推文与按创建时间排序的转推混合在一起(因为这是大多数时间会选择的内容)。恐怕查询会太复杂..
    • @jakubka:更新了一些示例架构、数据和查询的帖子。
    • @MikePurcell 问题是我们也需要按 retweet_time 对其进行排序。所以当tweet没有转推时,使用tweet_created时间,否则(tweet有转推)使用retweet_created。在您在最终表中的示例中,id 序列必须是(从上到下):3、7、6、5、4、2、1。所以在第 3 行的 created_at 列中将是转推日期(因为它是转推)在这种情况下是 2012-07-27 00:07:11。
    【解决方案2】:

    我会选择选项 2,稍作修改。如果不是转推,推文表中的列parent_id 应指向自身。然后,查询将非常容易:

    SELECT tm.Id, tm.UserId, tc.Text, tm.Created, 
        CASE WHEN tm.Id <> tc .Id THEN tm.UserId ELSE NULL END AS OriginalAsker
    FROM tweet tm
    LEFT JOIN tweet tc ON tm.ParentId = tc.Id
    ORDER BY tm.Created DESC
    

    tc 是父表 - 有内容的表。它有推文的文本、原始海报的 ID 等)

    如果不转发则引入关于指向自身的规则的原因是这样很容易向原始推文添加更多连接。您只需加入 tc 的表,而不在乎是否转发。

    查询不仅简单,而且比选项 1执行得更好,因为排序只使用一个可以索引的物理列。

    唯一的缺点是DB会大一点。

    【讨论】:

    • 根据我的经验,自联接表会变得复杂,尤其是当用户选择使用 ORM 时。此外,order by 在 created_at 列上,具有高基数,因此索引该列可能不是最佳选择。
    • @MikePurcell 关于索引问题的非常好的评论。将不得不了解更多信息。
    • 是的,我的 ORM 会遇到自联接问题,所以请注意任何想要执行选项 2 的人。IMO,选项 1 更好,因为它分离并定义了一个新结构......转发应该是这样的。
    猜你喜欢
    • 2020-08-18
    • 2011-02-24
    • 1970-01-01
    • 1970-01-01
    • 2012-07-11
    • 2013-12-22
    • 1970-01-01
    • 1970-01-01
    • 2019-02-08
    相关资源
    最近更新 更多