MySQL GROUP BY DateTime +/- 3 секунды - PullRequest
14 голосов
/ 01 июля 2011

Предположим, у меня есть таблица с 3 столбцами:

  • id (PK, int)
  • метка времени (datetime)
  • title (text)

У меня есть следующие записи:

1, 2010-01-01 15:00:00, Some Title
2, 2010-01-01 15:00:02, Some Title
3, 2010-01-02 15:00:00, Some Title

Мне нужно сделать записи GROUP BY, которые находятся в пределах 3 секунд друг от друга.Для этой таблицы строки 1 и 2 будут сгруппированы вместе.

Здесь есть похожий вопрос: Mysql DateTime group через 15 минут

Я также нашел это: http://www.artfulsoftware.com/infotree/queries.php#106

Я не знаю, как преобразовать эти методы во что-то, что будет работать в течение нескольких секунд.Проблема метода SO в том, что мне кажется, что он будет работать только для записей, попадающих в интервал времени, который начинается в известной точке.Например, если бы я заставил FLOOR() работать с секундами, с интервалом в 5 секунд время 15:00:04 было бы сгруппировано с 15:00:01, но не сгруппировано с 15:00:06.

Имеет ли это смысл?Пожалуйста, дайте мне знать, если необходимы дальнейшие разъяснения.

РЕДАКТИРОВАТЬ: Для набора чисел, {1, 2, 3, 4, 5, 6, 7, 50, 51, 60}, возможно, было бы лучше сгруппировать их {1, 2, 3, 4, 5, 6, 7}, {50, 51}, {60}, чтобы каждая строка группировки зависела от того, находится ли строка в пределах 3секунд предыдущего.Я знаю, что это немного меняет вещи, я прошу прощения за то, что не очень хорошо разбираюсь в этом.

Я пытаюсь нечетко сопоставлять журналы с разных серверовСервер № 1 может регистрировать элемент «Элемент № 1», а Сервер № 2 регистрирует этот же элемент «Элемент № 1» в течение нескольких секунд после получения сервером № 1.Мне нужно сделать несколько агрегатных функций в обеих строках журнала.К сожалению, у меня есть только название, из-за природы серверного программного обеспечения.

Ответы [ 5 ]

14 голосов
/ 02 июля 2011

Я использую отличную идею Тома Х., но делаю это немного по-другому:

Вместо того чтобы найти все строки, являющиеся началом цепочек, мы можем найти все времена , которые являются началами цепочек, а затем вернуться назад и найти строки, соответствующие времени.

В запросе № 1 здесь должно быть указано, в какие моменты начинаются цепочки, путем определения того, какое время не находится ни разу ниже их, но в течение 3 секунд:

SELECT DISTINCT Timestamp
FROM Table a
LEFT JOIN Table b
ON (b.Timestamp >= a.TimeStamp - INTERVAL 3 SECONDS
    AND b.Timestamp < a.Timestamp)
WHERE b.Timestamp IS NULL

И затем для каждой строки мы можем найти самую большую временную метку начала цепочки, которая меньше, чем наша временная метка с Query # 2:

SELECT Table.id, MAX(StartOfChains.TimeStamp) AS ChainStartTime
FROM Table
JOIN ([query #1]) StartofChains
ON Table.Timestamp >= StartOfChains.TimeStamp
GROUP BY Table.id

Как только мы получим это, мы сможем сделать это, как вы хотели.

SELECT COUNT(*) --or whatever
FROM Table
JOIN ([query #2]) GroupingQuery
ON Table.id = GroupingQuery.id
GROUP BY GroupingQuery.ChainStartTime

Я не совсем уверен, что это достаточно отличается от ответа Тома Х, который будет опубликован отдельно, но похоже, что у вас возникли проблемы с реализацией, и я подумал об этом, поэтому я решил опубликовать снова. Удачи!

5 голосов
/ 01 июля 2011

Теперь, когда я думаю, что понимаю вашу проблему, основываясь на ваших комментариях к OMG Ponies, я думаю, что у меня есть решение на основе множеств. Идея состоит в том, чтобы сначала найти начало любой цепочки на основе названия. Начало цепочки будет определяться как любая строка, в которой нет совпадений в течение трех секунд до этой строки:

SELECT
    MT1.my_id,
    MT1.title,
    MT1.my_time
FROM
    My_Table MT1
LEFT OUTER JOIN My_Table MT2 ON
    MT2.title = MT1.title AND
    (
        MT2.my_time < MT1.my_time OR
        (MT2.my_time = MT1.my_time AND MT2.my_id < MT1.my_id)
    ) AND
    MT2.my_time >= MT1.my_time - INTERVAL 3 SECONDS
WHERE
    MT2.my_id IS NULL

Теперь мы можем предположить, что любые нецепные стартеры принадлежат цепному стартеру, который появился перед ними. Так как MySQL не поддерживает CTE, вы можете добавить вышеприведенные результаты во временную таблицу, так как это сэкономит вам несколько соединений в одном и том же подзапросе ниже.

SELECT
    SQ1.my_id,
    COUNT(*)  -- You didn't say what you were trying to calculate, just that you needed to group them
FROM
(
    SELECT
        MT1.my_id,
        MT1.title,
        MT1.my_time
    FROM
        My_Table MT1
    LEFT OUTER JOIN My_Table MT2 ON
        MT2.title = MT1.title AND
        (
            MT2.my_time < MT1.my_time OR
            (MT2.my_time = MT1.my_time AND MT2.my_id < MT1.my_id)
        ) AND
        MT2.my_time >= MT1.my_time - INTERVAL 3 SECONDS
    WHERE
        MT2.my_id IS NULL
) SQ1
INNER JOIN My_Table MT3 ON
    MT3.title = SQ1.title AND
    MT3.my_time >= SQ1.my_time
LEFT OUTER JOIN
(
    SELECT
        MT1.my_id,
        MT1.title,
        MT1.my_time
    FROM
        My_Table MT1
    LEFT OUTER JOIN My_Table MT2 ON
        MT2.title = MT1.title AND
        (
            MT2.my_time < MT1.my_time OR
            (MT2.my_time = MT1.my_time AND MT2.my_id < MT1.my_id)
        ) AND
        MT2.my_time >= MT1.my_time - INTERVAL 3 SECONDS
    WHERE
        MT2.my_id IS NULL
) SQ2 ON
    SQ2.title = SQ1.title AND
    SQ2.my_time > SQ1.my_time AND
    SQ2.my_time <= MT3.my_time
WHERE
    SQ2.my_id IS NULL

Это выглядело бы намного проще, если бы вы могли использовать CTE или временную таблицу. Использование временной таблицы также может повысить производительность.

Кроме того, будут проблемы с этим, если у вас могут быть точные метки времени. Если это так, то вам нужно немного настроить запрос, чтобы использовать комбинацию идентификатора и метки времени, чтобы различать строки с соответствующими значениями метки времени.

РЕДАКТИРОВАТЬ: Изменены запросы для обработки точных совпадений по отметке времени.

2 голосов
/ 12 марта 2013

Простой запрос:

SELECT * FROM time_history GROUP BY ROUND(UNIX_TIMESTAMP(time_stamp)/3);
2 голосов
/ 01 июля 2011

Мне нравится ответ @Chris Cunningham, но вот другой взгляд на него.

Во-первых, мое понимание вашей постановки проблемы (поправьте меня, если я ошибаюсь):

Вы хотите посмотреть свой журнал событий как последовательность, упорядоченную по времени события, и разделить его на группы, определяя границу как интервал более 3 секунд между двумя соседними строками в последовательности.

Я работаю в основном на SQL Server, поэтому использую синтаксис SQL Server. Это не должно быть слишком сложно для перевода в MySQL SQL.

Итак, сначала наша таблица журнала событий:

--
-- our event log table
--
create table dbo.eventLog
(
  id       int          not null ,
  dtLogged datetime     not null ,
  title    varchar(200) not null ,

  primary key nonclustered ( id ) ,
  unique clustered ( dtLogged , id ) ,

)

Учитывая вышеизложенное понимание постановки задачи, следующий запрос должен дать вам верхнюю и нижнюю границы ваших групп. Это простой вложенный оператор выбора с 2 group by, чтобы свернуть вещи:

  • Самый внутренний select определяет верхнюю границу каждой группы. Эта верхняя граница определяет группу.
  • Внешний select определяет нижнюю границу каждой группы.

Каждая строка в таблице должна попадать в одну из определенных таким образом групп, и любая данная группа вполне может состоять из одного значения даты / времени.

[отредактировано: верхняя граница является самым низким значением даты / времени, где интервал больше 3 секунд]

select dtFrom = min( t.dtFrom ) ,
       dtThru =      t.dtThru
from ( select dtFrom = t1.dtLogged ,
              dtThru = min( t2.dtLogged )
       from      dbo.EventLog t1
       left join dbo.EventLog t2 on t2.dtLogged >= t1.dtLogged
                                and datediff(second,t1.dtLogged,t2.dtLogged) > 3
       group by t1.dtLogged
     ) t
group by t.dtThru

Затем вы можете извлечь строки из журнала событий и пометить их группой, к которой они принадлежат:

select *
from ( select dtFrom = min( t.dtFrom ) ,
              dtThru =      t.dtThru
       from ( select dtFrom = t1.dtLogged ,
                     dtThru = min( t2.dtLogged )
              from      dbo.EventLog t1
              left join dbo.EventLog t2 on t2.dtLogged >= t1.dtLogged
                                       and datediff(second,t1.dtLogged,t2.dtLogged) > 3
              group by t1.dtLogged
            ) t
       group by t.dtThru
     ) period
join dbo.EventLog t on t.dtLogged >=           period.dtFrom
                   and t.dtLogged <= coalesce( period.dtThru , t.dtLogged )
order by period.dtFrom , period.dtThru , t.dtLogged

Каждая строка помечена своей группой через возвращенные столбцы dtFrom и dtThru. Если хотите, вы можете придумать и назначить целое число строк каждой группе.

2 голосов
/ 01 июля 2011

Предупреждение: длинный ответ. Это должно работать и довольно аккуратно, за исключением одного шага в середине, когда вы должны снова и снова запускать оператор INSERT, пока он ничего не сделает, поскольку мы не можем делать рекурсивные вещи CTE в MySQL.

Я собираюсь использовать эти данные в качестве примера вместо ваших:

id    Timestamp
1     1:00:00
2     1:00:03
3     1:00:06
4     1:00:10

Вот первый запрос для записи:

SELECT a.id as aid, b.id as bid
FROM Table a
JOIN Table b 
ON (a.Timestamp is within 3 seconds of b.Timestamp)

Возвращает:

aid     bid
1       1
1       2
2       1
2       2
2       3
3       2
3       3
4       4

Давайте создадим хорошую таблицу для хранения вещей, которые не допускают дублирования:

CREATE TABLE
Adjacency
( aid INT(11)
, bid INT(11)
, PRIMARY KEY (aid, bid) --important for later
)

Теперь задача состоит в том, чтобы найти что-то вроде транзитивного замыкания этого отношения.

Для этого давайте найдем следующий уровень ссылок. под этим я подразумеваю, поскольку в таблице Смежности у нас 1 2 и 2 3, мы должны добавить 1 3:

INSERT IGNORE INTO Adjacency(aid,bid)
SELECT adj1.aid, adj2.bid
FROM Adjacency adj1
JOIN Adjacency adj2
ON (adj1.bid = adj2.aid)

Это не элегантная часть: вам нужно будет много раз запускать приведенный выше оператор INSERT, пока он не добавит строки в таблицу. Я не знаю, есть ли хороший способ сделать это.

Как только это закончится, у вас будет транзитивно-замкнутое отношение, подобное этому:

aid     bid
1       1
1       2
1       3     --added
2       1
2       2
2       3
3       1     --added
3       2
3       3
4       4

А теперь по прямой:

SELECT aid, GROUP_CONCAT( bid ) AS Neighbors
FROM Adjacency
GROUP BY aid

возвращается:

aid     Neighbors
1       1,2,3
2       1,2,3
3       1,2,3
4       4

So

SELECT DISTINCT Neighbors
FROM (
     SELECT aid, GROUP_CONCAT( bid ) AS Neighbors
     FROM Adjacency
     GROUP BY aid
     ) Groupings

возвращает

Neighbors
1,2,3
4

Уф!

...