处理访问日志的算法

时间:2013-04-02 22:18:54

标签: php mysql algorithm logging analytics

假设我有一个如下所示的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万次展示,因此我正在尝试优化此报告生成器。

2 个答案:

答案 0 :(得分:1)

如果用户“会话”的概念对您的分析很重要,那么我会开始在您的表中记录数据,以便查询与会话相关的数据并不是一个痛苦的过程。一种简单的方法是记录您的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分钟时都会更改。一旦用户值递增,我将为前一个用户插入更新session_id值,然后将temp数组重置为空,并继续与下一个用户进行该过程。

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

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

每天的独特会话:

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字段的索引。

答案 1 :(得分: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