【问题标题】:Grouping serial posts in a user feed在用户供稿中对连续帖子进行分组
【发布时间】:2012-05-10 21:48:44
【问题描述】:

我有一个图片帖子的用户供稿。每个用户都可以发布单个图像,但是,他可以经常重复该动作。比如说,在一小时内上传几张图片。

我如何有效地设计数据库表,以便当用户在一小时内发布多张图片(一张一张)时——我可以轻松地将这些 serial 帖子组合在一起,例如在 INSERT 上还是在 SELECT 上?


不建议多上传表单。事实并非如此:我刚刚用更常用的术语描述了这项任务 :)

【问题讨论】:

    标签: mysql database-design grouping feed


    【解决方案1】:

    你可以为每篇文章存储一个时间戳,然后从下一个中选择时间戳小于某个阈值的每个项目吗?

    另一个想法是在每个帖子中同时存储时间戳和“组号”。在存储帖子之前,执行SELECT 以查找在最后n 分钟内提交的帖子。如果您找到一个,请为新帖子使用相同的组号。如果您不这样做,则增加新帖子的组号。然后您可以按组号选择以找到您想要的项目。

    【讨论】:

    • 任何列都是可能的,包括时间戳。但是如何选择,使“关闭”的行粘在一起?
    • 已更新。当您尝试解决此类问题时,请忘记计算机和数据库;请从逻辑上思考一下:如何将多个项目“关联”在一起?
    • 当然!但是,我不确定这是最有效的方法 :) 如果我使用您的建议 - 那么我将不得不使用 this technique 来显示提要
    • 效率最高?可能不是。简单、足够高效、易于实施?是的。此外,至少到目前为止,这是这里建议的最有效的实现。 ;-P
    • 进行棘手的时间查询也并不总是非常有效。当您决定只想选择最近的一组或三个时,它只会变得更加困难。我喜欢在插入之前识别记录组的想法,但我也会存储一个时间戳,以防需要更改机会窗口。
    【解决方案2】:

    我想数据模型应该是这样的:

    请注意确保帖子之间的时间差异大于 TIMESTAMP 的分辨率(或准备好优雅地处理 PK 违规)。

    在支持分析功能的 DBMS 中,您可以相当轻松地将时间上接近的帖子分组。例如,Oracle 查询分组(针对给定用户)的帖子(针对给定用户)彼此相隔一小时,如下所示:

    SELECT T.*, SUM(DIFF) OVER (ORDER BY TIMESTAMP) GROUPING
    FROM (
        SELECT
            IMAGE.*,
            CASE
                WHEN TIMESTAMP <= LAG(TIMESTAMP) OVER (ORDER BY TIMESTAMP)
                    + INTERVAL '1' HOUR
                THEN 0
                ELSE 1
                END DIFF
        FROM IMAGE
        WHERE USER_ID = :user_id
    ) T;
    

    生成的 GROUPING 字段将识别其 TIMESTAMP“足够接近”的各个行组。这个查询也非常有效——它只是对 PK 索引的范围扫描。你可以在SQL Fiddle玩。

    不幸的是,MySQL 不支持分析功能,但在应用程序级别上做基本相同的功能应该没有问题。就SELECT ... ORDER BY TIMESTAMP,线性遍历结果,看看当前行和上一行有什么区别。

    【讨论】:

    • 使用行之间的时间差仅在每个用户的连续帖子不会被其他用户的帖子“打断”时才有效:)
    • @o_OTync 我不确定我是否理解。即使其他用户同时发布图像(有WHERE USER_ID = :user_id),此模式也使您能够有效地进行每用户分组。你不同意这个前提,还是你需要做其他事情?
    • 抱歉,我误解了您的查询 :) 不幸的是,我需要 MySQL,这无济于事 :(
    【解决方案3】:

    那是游乐场:

    CREATE TABLE `feed`(
      `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
      `tm` INT UNSIGNED NOT NULL COMMENT 'timestamp',
      `user_id` INT UNSIGNED NOT NULL COMMENT 'author id',
      `image` VARCHAR(255) NOT NULL COMMENT 'posted image filename',
      `group` INT UNSIGNED NULL DEFAULT NULL COMMENT 'post group',
      PRIMARY KEY(`id`),
      INDEX(`user_id`),
      INDEX(`tm`,`group`)
      );
    

    我们想将时间上接近的帖子组合在一起。

    首先,声明所需的粒度:时间接近度的阈值:

    SET @granularity:=60*60;
    

    每一行形成一个组,组 ID 与行 ID 匹配(也可以是时间戳):

    SELECT `g`.`id` AS `group`
    FROM `feed` `g`;
    

    每个组包含来自同一用户的行,发布时间早于组形成者:

    SELECT `g`.`id` AS `group`, `f`.*
    FROM `feed` `g`
        CROSS JOIN `feed` `f`
        ON (`f`.`user_id` = `g`.`user_id` 
            AND `f`.`tm` BETWEEN `g`.`tm`-@granularity AND `g`.`tm`
        )
    

    每一行属于多个组。对于每一行,我们选择最“广泛”的组:它具有最大的 rowId

    SELECT MAX(`g`.`id`) AS `group`, `f`.*
    FROM `feed` `g`
        CROSS JOIN `feed` `f`
        ON (`f`.`user_id` = `g`.`user_id` 
            AND `f`.`tm` BETWEEN `g`.`tm`-@granularity AND `g`.`tm`
        )
    GROUP BY `f`.`id`
    

    最近更新的组总是跳到顶部(如果您按group DESC 排序)。 但是,如果您希望组是持久的(例如,这样项目不会从一个组移动到另一个组),请使用 MIN 而不是 MAX

    SELECT MIN(`g`.`id`) AS `group`, `f`.*
    FROM `feed` `g`
        CROSS JOIN `feed` `f`
        ON (`f`.`user_id` = `g`.`user_id` 
            AND `f`.`tm` BETWEEN `g`.`tm` AND `g`.`tm`+@granularity
        )
    GROUP BY `f`.`id`
    

    现在,我们将更新表的 group 列。 首先,MySQL 无法更新您正在读取的同一张表。我们需要一个临时表。 第二:我们只更新group列为NULL的行,或者晚于UNIX_TIMESTAMP()-2*@threshold发布的行:

    CREATE TEMPORARY TABLE `_feedg`
    SELECT MAX(`g`.`id`) AS `group`, `f`.`id`
    FROM `feed` `g`
        CROSS JOIN `feed` `f`
        ON (`f`.`user_id` = `g`.`user_id` 
            AND `f`.`tm` BETWEEN `g`.`tm`-@granularity AND `g`.`tm`
        )
    WHERE `f`.`group` IS NULL 
        OR `f`.`tm` >= (UNIX_TIMESTAMP()-2*@granularity)
    GROUP BY `f`.`id`;
    

    并更新group 列:

    UPDATE `feed` `f` CROSS JOIN `_feedg` `g` USING(`id`)
    SET `f`.`group` = `g`.`group`;
    

    这是 SQLFiddle:http://sqlfiddle.com/#!2/be9ce/15

    【讨论】:

      【解决方案4】:

      “o_O Tync”的解决方案不会在 1 小时内对添加的项目进行分组,例如:1:00、1:40、2:30。只有最后两个会被分组。

      这是超快的 Mysql 解决方案,没有临时表和连接(同一张表)。

      创建表`饲料`( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `tm` INT UNSIGNED NOT NULL COMMENT 'timestamp', `user_id` INT UNSIGNED NOT NULL COMMENT 'author id', `image` VARCHAR(255) NOT NULL COMMENT '发布的图像文件名', `group` INT UNSIGNED NULL DEFAULT NULL COMMENT 'post group', 主键(`id`), 索引(`user_id`), 索引(`tm`,`组`) ); SET @粒度:=60*60; 更新提要 f CROSS JOIN ( 选择 g.id, @id:=COALESCE(IF(ISNULL(@prev_date) OR (user_id!=@prev_user_id) OR NOT(@prev_date-tm BETWEEN 0 AND @granularity), g.id, NULL), @id) +最少(0,@prev_date:=tm) +least(0, @prev_user_id:=user_id) 作为 group_id FROM (SELECT @prev_date:=null, @id:=null, @user_id:=null) r, feed g ORDER BY user_id DESC, tm DESC ) z 使用 (id) SET f.group = z.group_id;

      http://sqlfiddle.com/#!2/02a98/1/0

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-03-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多