【问题标题】:Algorithms for processing visit logs处理访问日志的算法
【发布时间】:2013-03-24 10:01:55
【问题描述】:

假设我有一个如下所示的 MySQL 表,我在其中跟踪用户 (User.id) 何时(日期)阅读我网站上的文章 (Article.id):

------------------------------------------
Article_Impressions
------------------------------------------
date                | user_id | article_id
--------------------+---------+-----------
2013-04-02 15:33:23 | 815     | 2342
2013-04-02 15:38:21 | 815     | 108
2013-04-02 15:39:33 | 161     | 4815
...

我正在尝试确定我有多少会话,以及给定日期每个用户的平均会话持续时间。当一篇文章在另一篇文章之后 30 分钟内没有被阅读时,会话结束。

问题

我如何才能有效地确定我在某一天有多少会话?我正在使用 PHP 和 MySQL。

我的第一个想法是查询给定日期的所有数据,按用户排序。然后我遍历每个用户,检查一次印象是否在最后一次印象的 30 分钟内,并统计每个用户当天的会话总数。

由于我们的网站每天有大约 200 万次展示,因此我正在尝试优化此报告生成器。

【问题讨论】:

  • 学习使用 GROUP BY 和 MySQL 聚合函数...这将是一个起点 - 不要尝试在 PHP 中完成所有工作
  • 我同意 Mark Ba​​ker 的观点,我会在 SQL 中做尽可能多的事情,可能会使用视图。您至少应该能够获得会话总数,但可能还可以获得每个用户的平均时间。
  • 我非常了解 MySQL。我肯定打算使用 MySQL 对数据进行排序和排序,但想不出任何其他方法。如何使用纯 SQL 按 30 分钟的存储桶对数据进行分组?做不到……
  • 将日期/时间转换为 unix 时间戳,除以 30 分钟,然后转换为整数,然后分组...将为您提供 30 分钟的存储桶
  • 但是会话是相对于上一次访问的,所以我需要查看上一次访问的时间,以确定从那时起已经过去了 30 分钟。如果用户每 29 分钟访问一个页面 100 次,这将被视为一个非常长的单次会话。部门/演员不会考虑到这一点......

标签: php mysql algorithm logging analytics


【解决方案1】:

试试这个查询

查询 1:

select 
  @sessionId:=if(@prevUser=user_id AND diff <= 1800 , @sessionId, @sessionId+1) as sessionId,
  @prevUser:=user_id AS user_id, 
  article_id,
  date,
  diff
from 
  (select @sessionId:=0, @prevUser:=0) b
join
  (select 
    TIME_TO_SEC(if(@prevU=user_id, TIMEDIFF(date, @prevD), '00:00')) as diff,
    @prevU:=user_id as user_id,
    @prevD:=date as date,
    article_id
  from 
    tbl 
  join
    (select @prev:=0, @prevU=0)a
  order by 
    user_id, 
    date) a

[结果]:

| SESSIONID | USER_ID | ARTICLE_ID |                DATE | DIFF |
-----------------------------------------------------------------
|         1 |     161 |       4815 | 2013-04-02 15:39:33 |    0 |
|         2 |     815 |       2342 | 2013-04-02 15:33:23 |    0 |
|         2 |     815 |        108 | 2013-04-02 15:38:21 |  298 |
|         3 |     815 |        108 | 2013-04-02 16:38:21 | 3600 |

如果根据您在问题中提到的要求,下一篇文章在 30 分钟后阅读,则此查询将为每个新用户以及同一用户返回一个唯一会话。 diff 列返回同一用户的 2 篇文章之间的秒差,这有助于我们计算 sessionId。现在使用此结果,您可以轻松计算每个用户的平均时间以及每个会话的总时间。

希望对你有帮助...

SQL Fiddle

【讨论】:

    【解决方案2】:

    如果用户“会话”的概念对您的分析很重要,那么我将开始在您的表中记录数据,以便查询与会话相关的数据不是一个痛苦的过程。一个简单的方法是记录您的 PHP 会话 ID。如果您的 PHP 会话 ID 设置为具有相同的 30 分钟到期时间,并且您将 PHP 会话 ID 记录到此表中,那么您基本上会得到您要查找的内容。

    当然,这对您现有的记录没有帮助。我可能会继续创建会话字段,然后用随机生成的“会话”ID 重新填充它。我不会为此寻找完整的 SQL 解决方案,因为它在处理边缘情况(跨天的会话等)方面可能无法满足您的要求。我会编写一个脚本来执行此回填,其中包含您需要的所有逻辑。

    我的一般方法是像这样选择所有记录:

    SELECT user_id, date /* plus any other fields like unique id that you would need for insert */
    FROM Article_Impressions
    WHERE session_id IS NULL
    ORDER BY user_id ASC, date ASC
    

    注意:确保您在 user_id 和 date 字段上都有索引。

    然后,我将遍历结果集,构建每个 user_id 的临时数组,并遍历该数组以获取所有日期值,并分配一个随机生成的会话 id,每次日期更改大于 30 分钟时该会话 id 都会更改。一旦用户值增加,我将为前一个用户进行插入以更新 session_id 值,然后将临时数组重置为空并继续与下一个用户进行该过程。

    请注意,像这样保持相对较小的临时/工作数组的方法可能很重要,因为您正在谈论的记录数量,您可能无法读取整个结果集放入内存中的数组中。

    填充数据后,查询变得微不足道:

    每天的独特会话:

    SELECT DATE(date) as `day`, COUNT(DISTINCT session_id) AS `unique_sessions`
    FROM Article_Impressions
    GROUP BY `day`
    ORDER BY `day` DESC /* or ASC depending on how you want to view it */
    

    每天的平均会话数:

    SELECT AVG(sessions_per_day.`unique_sessions`) AS `average_sessions_per_day`
    FROM
      (
        SELECT DATE(date) as `day`, COUNT(DISTINCT session_id) AS `unique_sessions`
        FROM Article_Impressions
        GROUP BY `day`
      ) AS sessions_per_day
    GROUP BY sessions_per_day.`day`
    

    注意:您需要在新的 session_id 字段上建立索引。

    【讨论】:

    • 将会话 ID 与其余数据一起保存听起来是个好主意。谢谢。
    猜你喜欢
    • 2010-11-04
    • 1970-01-01
    • 2020-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-14
    • 2015-01-03
    • 1970-01-01
    相关资源
    最近更新 更多