MySQL GROUP BY DateTime +/- 3秒

时间:2011-07-01 17:24:04

标签: mysql sql group-by datetime-generation

假设我有一个包含3列的表:

  • id(PK,int)
  • 时间戳(日期时间)
  • 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

我需要在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”。我需要在两个日志行上做一些聚合函数。不幸的是,由于服务器软件的性质,我只有标题可以继续。

5 个答案:

答案 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 22 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

每行都会通过返回的dtFromdtThru列标记其组。如果你愿意,你可以得到想象并为每个组分配一个完整的行号。

答案 4 :(得分:2)

简单查询:

SELECT * FROM time_history GROUP BY ROUND(UNIX_TIMESTAMP(time_stamp)/3);