2012-05-10 4 views
4

У меня есть фид сообщений изображений. Каждый пользователь может отправлять отдельные изображения, однако он может часто повторять действие. Скажем, загрузите несколько изображений в течение часа.Группировка последовательных сообщений в фиде пользователя

Как эффективно создать таблицу базы данных, так что, когда пользователь отправляет несколько изображений (один за другим) в течение одного часа, я могу легко сгруппировать эти последовательных сообщений вместе, вставить INSERT или SELECT?


Не предлагайте многоэтажную форму. Это не так: я только что описал задачу в более общих терминах :)

ответ

2

Это вне площадки:

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; 

Каждая строка образует группу с идентификатором группы, соответствующим идентификатор строки (он также может быть метку времени):

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`[email protected] 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`[email protected] 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`[email protected] 
    ) 
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`[email protected] 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

3

Можете ли вы сохранить временную метку с каждым сообщением, а затем выбрать каждый элемент, временная метка которого меньше порога от следующего?

Другой идеей было бы сохранить как метку времени, так и «номер группы» с каждым сообщением. Прежде чем хранить сообщение, выполните SELECT, чтобы просмотреть сообщения, которые были отправлены за последние n минут. Если вы найдете его, используйте тот же номер группы для нового сообщения. Если вы этого не сделаете, увеличьте номер группы для нового сообщения. Затем вы можете выбрать номер группы, чтобы найти нужные элементы.

+0

Любой столбец можно, в том числе временной метки. Но тогда, как выбирать, делая «близкие» ряды вместе? – kolypto

+0

Обновлено. Когда вы пытаетесь решить такую ​​проблему, забудьте о компьютере и базе данных; просто подумайте об этом логично: как вы можете «связать» несколько предметов вместе? –

+0

Конечно! Однако я не уверен, что это самый эффективный вариант :) Если я использую ваше предложение, тогда мне придется использовать [эту технику] (http://stackoverflow.com/questions/10542647/grouping-serial-posts -in-a-user-feed), чтобы отобразить фид – kolypto

2

Я полагаю, что модель данных будет выглядеть это похоже:

enter image description here

Только будьте осторожны, чтобы обеспечить временное различие между столбами больше, чем разрешение на TIMESTAMP (или быть готовым к обработке нарушений PK грациозно).

В СУБД, которая поддерживает аналитические функции, вы можете легко группировать сообщения, которые временно закрываются. Например, 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, линейно пересекайте результаты и посмотрите, какая разница между текущей и предыдущей строкой.

+0

Использование разницы во времени между строками работает только в том случае, если каждое сообщение об ошибках пользователя не прерывается сообщениями других пользователей :) – kolypto

+0

@o_OTync Я не уверен, что понимаю.Эта схема позволяет эффективно выполнять группировку для каждого пользователя, даже когда другие пользователи одновременно размещают изображения (есть 'WHERE USER_ID =: user_id'). Вы не согласны с этой предпосылкой или, возможно, вам нужно сделать что-то еще? –

+0

Извините, я неправильно понял ваш запрос :) К сожалению, мне нужен MySQL, и это не поможет :( – kolypto

1

Решение «o_O Tync» не будет группировать элементы в течение 1 часа, если они добавлены, например: 1:00, 1:40, 2:30. Только последние два будут сгруппированы.

Здесь находится сверхбыстрое решение Mysql без таблиц temp и соединений (одной таблицы).

 
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; 
UPDATE feed f CROSS JOIN (
    SELECT 
    g.id, 
    @id:=COALESCE(IF(ISNULL(@prev_date) OR ([email protected]_user_id) OR NOT(@prev_date-tm BETWEEN 0 AND @granularity), g.id, NULL), @id) 
    +least(0, @prev_date:=tm) 
    +least(0, @prev_user_id:=user_id) as group_id  
    FROM (SELECT @prev_date:=null, @id:=null, @user_id:=null) r, feed g 
    ORDER BY user_id DESC, tm DESC 
) z USING (id) 
SET f.group = z.group_id; 

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

Смежные вопросы