SQL基于30分钟的间隔提取会话

时间:2014-10-10 09:40:36

标签: sql dql gaps-and-islands

标题可能会留下一些问题,所以我会详细解释。

我有一个包含聊天消息的(MySQL)表,这些消息都包含添加日期的日期时间列。

现在我想要实现的是我获取已经拥有的会话数量。

现在,什么是会话?自上一条消息起持续30分钟或更长时间后,新会话开始。

例如,数据:

2014-01-01 00:00:01
2014-01-01 00:20:01
2014-01-01 00:40:01
2014-01-01 00:60:01

将是一个会议

2014-01-01 00:00:01 <--
2014-01-01 00:32:01 <--
2014-01-01 00:35:01
2014-01-01 01:00:01
2014-01-01 02:00:01 <--
2014-01-01 02:20:01

将重新开始三个会话,我将箭头放在开头。

我不需要DQL示例MySQL会没事的,希望有人可以帮我解决这个问题。

编辑:下面给出的答案似乎适用于小提琴,但不适用于运行5.5.4的MySQL服务器,想知道这是某个设置还是sqlFiddle没有按预期工作。

4 个答案:

答案 0 :(得分:1)

遗憾的是,MySQL没有所谓的窗口函数(大多数其他主要的RDBMS都这样做),所以我们必须假设我们自己的一个。这实际上并不是那么困难,但获得支持会很好......

无论如何,我说我们需要假设一个LAG()函数,但实际上我们可以比较增加一个分组计数器,所以我们实际上可以删除一个步骤,排序:

SELECT sentAt,
       @Session := IF(sentAt < @SessionBoundary, @Session, @Session + 1) AS session,
       @SessionBoundary := ADDTIME(sentAt, '00:30:00') AS sessionBoundary
FROM Message
JOIN (SELECT @Session := 0) n
ORDER BY sentAt

SQL Fiddle Example

重要的是,请注意,对于正向连续范围类型(​​如date/time/timestamps)使用“独占上限”(<)是一种好习惯,它是 30th < / strong>分钟实际开始你的新会话。也就是说,13:00的初始消息表示下一个会话在13:30开始(没有其他消息)。这有一个很好的特性,可以很好地排列一切,并且我不必担心我可能没有指定的小数秒的奇怪行为。
无论如何,这会返回如下结果:

sentAt               session  sessionBoundary
2014-01-01 00:00:01  1        2014-01-01 00:30:01 
2014-01-01 00:32:01  2        2014-01-01 01:02:01 
2014-01-01 00:35:01  2        2014-01-01 01:05:01 
2014-01-01 01:00:01  2        2014-01-01 01:30:01
2014-01-01 02:00:01  3        2014-01-01 02:30:01
2014-01-01 02:20:01  3        2014-01-01 02:50:01 

现在,既然你想要的只是一个简单的数量session,你可以把它包装成子查询:

SELECT MAX(session)
FROM (SELECT sentAt,
             @Session := IF(sentAt < @SessionBoundary, @Session, @Session + 1) AS session,
             @SessionBoundary := ADDTIME(sentAt, '00:30:00') AS sessionBoundary
      FROM Message
      JOIN (SELECT @Session := 0) n
      ORDER BY sentAt) MessageSession

SQL Fiddle Example
(注意:由于某些原因我不明白,使用初始工作作为子查询会导致小提琴从0开始而不是之前的1。请在您的服务器上测试,因为您可能需要初始化@Session = 1代替0,或使用类似COUNT(DISTINCT session)的内容。

......我们已经完成了。


虽然您只列出了想要计数,但是一旦您进行了会话分组,您就可以获得各种有趣的数据。现在获得MAX(sentAt) / MIN(sentAt)每组,计算组中的消息数量等等是微不足道的。例如,您可以通过以下方式说“查找所有长时间运行的会话”:

SELECT session, 
       MIN(sentAt) AS firstMessageAt, MAX(sentAt) AS lastMessageAt, COUNT(*) AS messages
FROM (SELECT sentAt,
             @Session := IF(sentAt < @SessionBoundary, @Session, @Session + 1) AS session,
             @SessionBoundary := ADDTIME(sentAt, '00:30:00') AS sessionBoundary
      FROM Message
      JOIN (SELECT @Session := 0) n
      ORDER BY sentAt) MessageSession
GROUP BY session
HAVING ADDTIME(MIN(sentAt), '24:00:00') < MAX(sentAt)

(查找已运行至少24小时的所有会话)

答案 1 :(得分:0)

对于我的特定情况,我使用如下查询解决了它:

SELECT  *
FROM    chat c
WHERE   NOT EXISTS
        (
        SELECT  *
        FROM    chat c2 
        WHERE   c2.date_add <= (c.date_add + INTERVAL 30 MINUTE)
        AND     c2.date_add > c.date_add
        )

这里唯一的问题是我无法看到会话何时开始,但这对我的特定情况来说已经足够了。我很乐意接受更好的答案!

答案 2 :(得分:0)

困难的部分是,当问题是程序性时,SQL是一种非过程语言。

您的提案是一个合理的查询,可以在纯SQL中完成。如果你想更进一步,我建议你使用一个程序脚本,可以用多种语言编写,Python,Perl,Ruby ......

在伪语言中,它可能是:

long last=-1800  // time of previous line
long beg = -1    // begin time for current session
CREATE_QUERY_FOR : SELECT UNIX_TIMESTAMP(dat) FROM chat ORDER BY dat
LOOP_PER_LINE_FETCHED getting dat

if (dat - last > 30 * 60)
then 
    if last != -1 // we had a session
    then NOTE SESSION begin at beg and ending at last
    endif

    beg = dat   // start a new session
endif

last = dat

END_LOOP

恕我直言,这是开始和结束所有会话的唯一方式(或者至少是最简单和最有效的方式) - 但我必须承认它可能不是你所要求的......

答案 3 :(得分:0)

SELECT  distinct *
FROM    chat c
WHERE   NOT EXISTS
        (
        SELECT  *
        FROM    chat c2 
        WHERE   c2.date_add > (c.date_add - INTERVAL 30 MINUTE)
        AND     c2.date_add < c.date_add
        )