我有一个图片帖子的用户供稿。每个用户都可以发布单个图像,但是,他可以经常重复该动作。比如说,在一小时内上传几张图片。
我如何有效地设计数据库表,以便当用户在一小时内发布多张图片(一张一张)时——我可以轻松地将这些连续帖子组合在一起,例如在 INSERT 或 SELECT 上?
不建议多上传表单。事实并非如此:我刚刚用更常见的术语描述了这项任务:)
我有一个图片帖子的用户供稿。每个用户都可以发布单个图像,但是,他可以经常重复该动作。比如说,在一小时内上传几张图片。
我如何有效地设计数据库表,以便当用户在一小时内发布多张图片(一张一张)时——我可以轻松地将这些连续帖子组合在一起,例如在 INSERT 或 SELECT 上?
不建议多上传表单。事实并非如此:我刚刚用更常见的术语描述了这项任务:)
您可以为每个帖子存储一个时间戳,然后从下一个中选择时间戳小于某个阈值的每个项目吗?
另一个想法是在每个帖子中存储时间戳和“组号”。在存储帖子之前,执行 a以查找在最后几分钟SELECT
内提交的帖子。n
如果您找到一个,请为新帖子使用相同的组号。如果您不这样做,则增加新帖子的组号。然后您可以按组号选择以找到您想要的项目。
我想数据模型看起来像这样:
只需注意确保帖子之间的时间差异大于 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
,线性遍历结果,看看当前行和上一行有什么区别。
那是操场:
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`;
每个组包含来自同一用户的行,发布时间早于 group-former:
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
“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;