假设我有一个包含3列的表:
我有以下记录:
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
我需要在3秒内完成GROUP BY记录。对于此表,第1行和第2行将组合在一起。
这里有一个类似的问题:Mysql DateTime group by 15 mins
我也发现了这个: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”。我需要在两个日志行上做一些聚合函数。不幸的是,由于服务器软件的性质,我只有标题可以继续。
答案 0 :(得分:14)
我正在使用Tom H.的优秀想法,但在这里做的有点不同:
我们可以找到作为链的起点的所有次,而不是找到所有链的起始行,然后返回并找出与时间匹配的行。
这里的查询#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
然后对于每一行,我们可以找到最小的链起始时间戳,该时间戳小于查询#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
我不完全确定这与汤姆H的答案分开是否足够明显,但听起来好像你在实施方面遇到了麻烦,我正在思考它,所以我想我会再次发帖。祝你好运!
答案 1 :(得分:5)
现在我认为我理解你的问题,基于你对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或使用临时表,这看起来会更简单。使用临时表也可能有助于提高性能。
此外,如果您可以准确匹配时间戳,则会出现此问题。如果是这种情况,那么您需要稍微调整查询以使用id和时间戳的组合来区分具有匹配时间戳值的行。
编辑:更改查询以按时间戳处理完全匹配。
答案 2 :(得分:2)
警告:答案很长。这应该工作,并且非常简洁,除了中间的一步,你必须愿意一遍又一遍地运行INSERT语句,直到它不做任何事情,因为我们不能在MySQL中做递归的CTE事情。 / p>
我将使用此数据作为示例而不是您的数据:
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
)
现在的挑战是找到类似该关系的transitive closure之类的东西。
为此,让我们找到下一级别的链接。我的意思是,由于我们在邻接表中有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
呼!
答案 3 :(得分:2)
我喜欢@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
列标记其组。如果你愿意,你可以得到想象并为每个组分配一个完整的行号。
答案 4 :(得分:2)
简单查询:
SELECT * FROM time_history GROUP BY ROUND(UNIX_TIMESTAMP(time_stamp)/3);