获取连续记录视图的持续时间

Posted

技术标签:

【中文标题】获取连续记录视图的持续时间【英文标题】:Get duration of consecutive record views 【发布时间】:2013-06-27 17:56:06 【问题描述】:

我有我的 Widget 销售人员的通话记录。记录客户记录中的每次点击。员工每天可能会多次访问同一个客户帐户,因此在一天中的不同时间,同一记录 ID 下可能会有数十次连续点击。

例子:

recordID    userID  date_event
33450   321     2013-06-20 16:22:02
33450   321     2013-06-20 16:22:02
33450   321     2013-06-20 16:22:24
33450   321     2013-06-20 16:22:24
22222   321     2013-06-20 16:22:53
22222   321     2013-06-20 16:22:54
12345   321     2013-06-20 16:23:43
12345   321     2013-06-20 16:23:44
12345   321     2013-06-20 16:24:00
12345   321     2013-06-20 16:24:05
12345   321     2013-06-20 16:24:05
12345   321     2013-06-20 18:16:09
12345   321     2013-06-20 18:16:09
33450   321     2013-06-20 18:33:24
33450   321     2013-06-20 18:35:11
33450   321     2013-06-20 18:36:55
12345   321     2013-06-20 19:01:14
98765   321     2013-06-20 19:02:43

在上面的数据集中,我有 6 组访问权限。

        first               last                    duration(seconds)
33450   2013-06-20 16:22:02 2013-06-20 16:22:24     22
22222   2013-06-20 16:22:30 2013-06-20 16:22:54     24
12345   2013-06-20 16:23:43 2013-06-20 18:16:09     6746
33450   2013-06-20 18:33:24 2013-06-20 18:36:55     211
12345   2013-06-20 19:01:14 2013-06-20 19:01:14     0
98765   2013-06-20 19:02:43 2013-06-20 19:02:43     0

持续时间是一个估计值,而不是眼睛注视记录的实际时间。我无法检测到员工何时积极使用此应用程序,或者他们何时使用客户端工具在另一个应用程序或网站中查找数据。

表结构为:

CREATE TABLE IF NOT EXISTS `record_log` (
  `event_id` int(11) NOT NULL AUTO_INCREMENT,
  `userID` int(5) DEFAULT NULL,
  `recordID` int(10) DEFAULT NULL,
  `date_event` datetime DEFAULT NULL,
  PRIMARY KEY (`event_id`),
  KEY `userID` (`userID`),
  KEY `date_event` (`date_event`),
  KEY `recordID` (`recordID`),
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

构造查询以返回第二个数据集的最佳方法是什么?这可以在一个查询中完成而不使用太多循环吗?在给定的一天,我可能会访问数千条记录。

【问题讨论】:

你有 auto_increment ID 吗?你的记录是如何组合在一起的?当记录 ID 发生变化时,组会发生变化吗? 是的 - auto_increment 是“event_id”。记录将按记录 ID 分组,但要注意的是,我需要的不仅仅是给定时间段内每个记录 ID 的所有持续时间的简单总计。在一天中可以多次访问相同的记录 ID。通常会有多组连续的活动。或者,一个recordID 可能在一天中只被访问过一次。我试图在上面的数据中解释并展示这一点。 【参考方案1】:

是的,可以返回指定的结果集,但它并不漂亮。特别是,使用内联视图(mysql 称它们为“派生表”)意味着行将被写入临时 MyISAM 表,并且该操作对于大型集合来说可能是昂贵的,因此使用这种方法获得性能将可能需要在最里面的查询上使用一些谓词才能获得一组合理大小的数据......例如

    WHERE q.userID = 321
      AND q.date_event >= '2012-01-01'
      AND q.date_event < '2012-01-02'

注意:“持续时间”的值似乎应该受到限制......即,您是否希望周五下午 6 点的点击与周一早上 8 点的点击“匹配”,这是否被视为“期间”。在下面的查询中,我将最大持续时间值指定为 6800 秒,这样 6800 是可以返回的最大持续时间,任何大于此的持续时间都会“拆分”为两个持续时间。

这是一个返回指定结果集的查询示例:

SELECT recordID
  -- , s.userID
     , s.first
     , MAX(s.date_event) AS `last`
     , MAX(TIMESTAMPDIFF(SECOND,s.first,s.date_event)) AS duration
  -- , MAX(s.cnt) AS `cnt`
  FROM ( 
         SELECT IF(r.recordID = @record_id AND r.userID = @user_id AND r.date_event < (@date_event + INTERVAL 6800 SECOND),
                @cnt := @cnt + 1, @cnt := 1) AS `cnt`
              , IF(r.recordID = @record_id AND r.userID = @user_id AND r.date_event < (@date_event + INTERVAL 6800 SECOND),
                @first, @first := r.date_event) + INTERVAL 0 SECOND AS `first`
              , @record_id := r.recordID AS recordID
              , @user_id := r.userID AS userID
              , @date_event := r.date_event  AS date_event 
           FROM ( SELECT @record_id := NULL, @user_id := NULL, @date_event := NULL, @cnt := 0, @first := NULL) i
           JOIN ( SELECT q.recordID, q.userID, q.date_event
                    FROM record_log q
                   ORDER BY q.userID, q.date_event, q.recordID
                 ) r
       ) s
 GROUP
    BY s.first
     , s.userID
     , s.recordID
 ORDER
    BY s.first
     , s.userID
     , s.recordID

注意:此查询假定一条记录上的“持续时间”将被另一条记录上的“持续时间”“分解”。 (如果用户点击了一条记录,然后点击了另一条记录,然后又返回到原始记录进行更多点击,则对原始记录的点击将被计为两个不同的持续时间。


样本数据:

INSERT INTO record_log (recordID, userID, date_event) VALUES
 ('33450','321','2013-06-20 16:22:02')
,('33450','321','2013-06-20 16:22:02')
,('33450','321','2013-06-20 16:22:24')
,('33450','321','2013-06-20 16:22:24')
,('22222','321','2013-06-20 16:22:53')
,('22222','321','2013-06-20 16:22:54')
,('12345','321','2013-06-20 16:23:43')
,('12345','321','2013-06-20 16:23:44')
,('12345','321','2013-06-20 16:24:00')
,('12345','321','2013-06-20 16:24:05')
,('12345','321','2013-06-20 16:24:05')
,('12345','321','2013-06-20 18:16:09')
,('12345','321','2013-06-20 18:16:09')
,('33450','321','2013-06-20 18:33:24')
,('33450','321','2013-06-20 18:35:11')
,('33450','321','2013-06-20 18:36:55')
,('12345','321','2013-06-20 19:01:14')
,('98765','321','2013-06-20 19:02:43')

【讨论】:

快速跟进。为什么使用q.date_event &gt;= '2012-01-01' AND q.date_event &lt; '2012-01-02' 而不是DATE(q.date_event) = '2012-01-01' 好问题。在谓词(WHERE 子句)中,我们不想将列包装在函数中,因为这样做会禁用列上的索引来满足查询。使用裸列上的范围,可以使用索引。但是如果我们使用DATE(q.date_event),那么这会有效地强制 MySQL 为表中的每一行计算表达式(即调用 DATE 函数)。 (实际上,这是最坏的情况;如果行首先被另一个谓词过滤掉,那么 MySQL 可以跳过评估已经过滤掉的行的表达式。)【参考方案2】:

我会使用带变量的 SQL 查询:

SELECT
  recordID,
  userID,
  MIN(date_event) first,
  MAX(date_event) last,
  TIME_TO_SEC(TIMEDIFF(MAX(date_event), MIN(date_event))) sec
FROM (
  SELECT
    events.*,
    CASE WHEN @last_recordID=recordID THEN @grp ELSE @grp:=@grp+1 END groupID,
    @last_recordID := recordID
  FROM
    events, (SELECT @grp:=0, @last_recordID:=NULL) r
  ORDER BY
    event_ID
  ) s
GROUP BY
  recordID,
  userID,
  groupID
ORDER BY
  groupID

请看小提琴here。

【讨论】:

【参考方案3】:

返回数据集的最简单查询是:

SELECT recordID, MIN(date_event) AS `first`, MAX(date_event) AS `last`
, TIMESTAMPDIFF(SECOND, MIN(date_event), MAX(date_event)) AS `duration(seconds)`
FROM `record_log`
GROUP BY recordID

另一个可能更快的选择是计算每次访问客户记录时的合理持续时间估计值。下面的查询每次访问使用 30 秒的持续时间:

SELECT recordID, COUNT(*) AS staff_clicks, 30*COUNT(*) AS `estimated duration(seconds)`
FROM `record_log`
GROUP BY recordID

这些都是非常基本的,但它们确实回答了您最初的问题。有很多选择,但如果没有更多信息(时间限制、所需的准确度等),很难知道要开什么处方

【讨论】:

这确实是更简单的查询。不幸的是,它不返回指定的结果集。例如,对于 recordID 33450,它将返回一行,持续时间为 2013-06-20 16:22:02 - 2013-06-20 18:36:55,而不是规范中显示的两个单独的持续时间。

以上是关于获取连续记录视图的持续时间的主要内容,如果未能解决你的问题,请参考以下文章

使用 SQL 中的巧妙语句以连续方式将列值更改为多条记录

zynq7020开发记录(持续更新)--PS和PL间的数据交互

连续检查 MySQL 表更新的最佳方法是啥?

连续几小时内的 MYSQL SUM 持续时间

Oracle中获取连续的序列号范围的SQL

SQL Server索引