MySQL GROUP BY DateTime +/- 3 секунды
Предположим, что у меня есть таблица с тремя столбцами:
- id (PK, int)
- timestamp (datetime)
- title (текст)
У меня есть следующие записи:
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 на 15 минут
Я также нашел это: http://www.artfulsoftware.com/infotree/queries.php#106
Я не знаю, как преобразовать эти методы во что-то, что будет работать в течение нескольких секунд. Проблема с методом в вопросе SO заключается в том, что мне кажется, что он будет работать только для записей, попадающих в промежуток времени, который начинается с известной точки. Например, если бы я должен был FLOOR()
работать со секундами, с интервалом в 5 секунд, время 15:00:04 было бы сгруппировано с 15:00:01, но не сгруппировано с 15:00:06.
Это имеет смысл? Пожалуйста, дайте мне знать, если потребуется дополнительное разъяснение.
EDIT:. Для набора чисел {1, 2, 3, 4, 5, 6, 7, 50, 51, 60} кажется, что лучше всего их сгруппировать { 1, 2, 3, 4, 5, 6, 7}, {50, 51}, {60}, так что каждая строка группировки зависит от того, находится ли строка в пределах 3 секунд от предыдущей. Я знаю, что это немного меняет вещи, я сожалею о том, что хочу быть в этом желающим.
Я пытаюсь вести журналы с нечеткой совпадением с разных серверов. Сервер № 1 может регистрировать элемент, "Item # 1", а Server # 2 будет регистрировать тот же элемент "Item # 1" в течение нескольких секунд после сервера # 1. Мне нужно выполнить некоторые общие функции на обеих строках журнала. К сожалению, у меня есть только заголовок, связанный с природой серверного программного обеспечения.
Ответы
Ответ 1
Я использую отличную идею Tom H., но делаю это несколько иначе:
Вместо того, чтобы найти все строки, которые являются началами цепей, мы можем найти все times, которые являются началами цепей, затем верните назад и ifnd строки, соответствующие временам.
Запрос № 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
Как только мы получим это, мы можем GROUP BY по вашему желанию.
SELECT COUNT(*) --or whatever
FROM Table
JOIN ([query #2]) GroupingQuery
ON Table.id = GroupingQuery.id
GROUP BY GroupingQuery.ChainStartTime
Я не совсем уверен, что это достаточно отчетливо от ответа Тома Х, чтобы его публиковали отдельно, но это звучало так, будто у вас были проблемы с реализацией, и я думал об этом, поэтому я думал, что снова опубликую. Удачи!
Ответ 2
Теперь, когда я думаю, что я понимаю вашу проблему, основанный на ответе на комментарий 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 или использовать временную таблицу. Использование временной таблицы также может помочь в производительности.
Кроме того, будут проблемы с этим, если вы можете точно указать метки времени. Если в этом случае вам нужно немного подправить запрос, чтобы использовать комбинацию идентификатора и метки времени, чтобы различать строки с соответствующими значениями отметки времени.
EDIT: Изменены запросы для обработки точных совпадений по метке времени.
Ответ 3
Предупреждение: длинный ответ. Это должно работать и довольно аккуратно, за исключением одного шага в середине, где вы должны быть готовы запускать инструкцию 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
)
Теперь задача состоит в том, чтобы найти что-то вроде транзитного закрытия этого отношения.
Чтобы сделать это, найдите следующий уровень ссылок. я имею в виду, поскольку в таблице Adjacency мы имеем 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
Итак,
SELECT DISTINCT Neighbors
FROM (
SELECT aid, GROUP_CONCAT( bid ) AS Neighbors
FROM Adjacency
GROUP BY aid
) Groupings
возвращает
Neighbors
1,2,3
4
Уф!
Ответ 4
Мне нравится @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 ) ,
)
Учитывая описанное выше описание проблемы, следующий запрос должен дать вам верхнюю и нижнюю границы ваших групп. Это простой, вложенный оператор select с 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
. Вы можете получить фантазию и назначить целочисленный номер строки каждой группе, если хотите.
Ответ 5
Простой запрос:
SELECT * FROM time_history GROUP BY ROUND(UNIX_TIMESTAMP(time_stamp)/3);