如何获取具有特定值的下一条记录,然后返回一条记录以从日志计算会话持续时间?

时间:2014-02-05 05:17:13

标签: mysql sql subquery moodle

一般问题

给定给定用户的特定记录,我想获得具有相同列值的该用户的下一条记录,然后找到该用户的上一条记录。

+----+--------+--------+
| id | userid | action |
+----+--------+--------+
|  1 |      2 | a      |
| 20 |      2 | b      |
| 21 |      2 | c      |
| 22 |      2 | c      |
| 23 |      2 | d      |
| 59 |      2 | a      |
| 60 |      2 | b      |
| 71 |      2 | c      |
| 72 |      2 | c      |
| 83 |      2 | d      |
| 99 |      2 | a      |
+----+--------+--------+

所以我想返回以下内容:

+--------+---------+----------+
| userid | left.id | right.id |
+--------+---------+----------+
| 2      | 1       | 23       |
| 2      | 59      | 83       |
+--------+---------+----------+

我想要实现的具体例子

我试图从Moodle的日志表中估算会话持续时间以进行报告。

例如,用户将登录,生成带有module = user和action = login的日志。如果他们注销,那么这将创建一个带有module = user和action = logout的日志,但这仅在大约20%的情况下发生。登录后将发生一系列其他日志。

可以使用此20%作为平均持续时间计算的样本,但报告要求对每个用户进行近似。

当前的报表工具集成是MySQL驱动的,它提示纯粹在SQL而不是PHP中执行此操作。

我做了什么

所以我将它构建为使用子查询的查询,如下所示:

  1. 查找现有登录条目
  2. 查找下一个登录条目
  3. 在下次登录前将自己的现有条目加入上一个条目
  4. 这似乎有效,但整个数据集的性能相当差。总共有几百万行,但通常报告会对每周或每月总结感兴趣。

    我的问题是,是否有更好的解决方法?

    根据课程,部门等,有更广泛的要求将从此演变为总持续时间报告,因此最佳SQL对此至关重要。

    SQLFiddle

    使用子查询:http://sqlfiddle.com/#!2/42d5ce/6

    的MySQL

    SELECT l.userid, FROM_UNIXTIME(l.time) as start,
           FROM_UNIXTIME(r.time) as end, (r.time - l.time) AS duration
    FROM mdl_log AS l 
    INNER JOIN mdl_log AS r ON r.id = (
        SELECT n.id
        FROM mdl_log n
        WHERE n.id < (
          SELECT id 
          FROM mdl_log t
          WHERE l.userid = t.userid
            AND t.time > l.time 
            AND t.module = 'user' 
            AND t.action = 'login'
          LIMIT 0,1
        )
        AND l.userid = n.userid
        ORDER BY n.id DESC
        LIMIT 0,1
    )
    WHERE l.module = 'user'
      AND l.action = 'login'
    

1 个答案:

答案 0 :(得分:0)

也许有点像PHP。注销是关键,因此只会使用注销之前的登录 - 当然它需要处于用户ID +时间顺序。

然后我会使用flexi表来显示结果 - 但是你需要为分页做更多的工作。

我也使用get_recordset_sql()而不是get_records(),因为可能会有很多记录。

$sql = "SELECT l.userid,
                l.time AS timeaction,
                CASE WHEN l.action = 'login' THEN l.time ELSE 0 END AS timestart,
                CASE WHEN l.action = 'logout' THEN l.time ELSE 0 END AS timeend
        FROM {log} l
        WHERE l.module = 'user'
        AND l.action IN ('login', 'logout')
        ORDER BY l.userid, l.time";

$logs = $DB->get_recordset_sql($sql);
$sessions = array();
if ($logs->valid()) {
    $userid = 0;
    $timestart = 0;
    $timeend = 0;
    foreach ($logs as $log) {
        if (!empty($log->timestart)) {
            // Logged in.
            $userid = $log->userid;
            $timestart = $log->timestart;
        } else if (!empty($log->timeend)) {
            // Logged out.
            $session = new stdClass();
            $session->userid = $userid;
            $session->timestart = $timestart;
            $session->timeend = $log->timeend;
            $sessions[] = $session;
        }
    }
    $logs->close(); // Required for recordset.

    // Use a flexitable to display the results properly with paging.
    foreach ($sessions as $session) {
        echo 'Userid : ' . $session->userid . 
            ' Time start ' . gmdate("Y-m-d H:i:s", $session->timestart) . 
            ' Time end ' . gmdate("Y-m-d H:i:s", $session->timeend)  .
            ' Duration ' . gmdate("H:i:s", $session->timeend - $session->timestart) . '<br/>';
    }
}