日志中每天的每周活跃用户
Posted
技术标签:
【中文标题】日志中每天的每周活跃用户【英文标题】:Weekly Active Users for each day from log 【发布时间】:2012-12-14 18:50:36 【问题描述】:我想知道是否有人可以帮助我使用一些 SQL 来返回两天或更长时间内登录到数据库表的唯一用户的数量(让我们使用 7 天作为参考)。
我的日志表在每一行中都包含一个时间戳 (ts) 和 user_id,表示该用户当时的活动。
以下查询会从此日志中返回每日活跃用户或 DAU:
SELECT FLOOR(ts / 86400) AS day, COUNT(DISTINCT user_id) AS dau
FROM log
GROUP BY day ORDER BY day ASC
现在假设我想在这个单一查询中添加(或至少以最有效的方式检索)每周活跃用户,或在 7 天内登录的唯一用户总数。但是,我不想将我的时间分配在不重叠的几周内。我需要每天计算当天和前 6 天看到的不同 user_id。
例如:
day users wau
1 1,2 2
4 1,3 3
7 3,4,5 5
8 5 4 (user_id 2 lost from count)
15 2 2 (user_ids 1,3,4 lost from count)
感谢您提供的任何帮助,如果您需要进一步说明,请随时通过评论询问。
【问题讨论】:
【参考方案1】:要获得“每周平均用户”计数(根据我对您的规范的理解......“对于每一天,当天和前六天看到的不同 user_id 的计数”),查询如下可以使用下面的一个。 (该查询还返回“每日平均用户”计数。
SELECT d.day
, COUNT(DISTINCT u.user_id) AS wau
, COUNT(DISTINCT IF(u.day=d.day,u.user_id,NULL)) AS dau
FROM ( SELECT FLOOR(k.ts/86400) AS `day`
FROM `log` k
GROUP BY `day`
) d
JOIN ( SELECT FLOOR(l.ts/86400) AS `day`
, l.user_id
FROM `log` l
GROUP BY `day`, l.user_id
) u
ON u.day <= d.day
AND u.day > d.day - 7
GROUP BY d.day
ORDER BY d.day
(我还没有对此进行测试;但我稍后会进行测试,如果需要任何更正,我会更新此声明。)
此查询将给定日期(来自u
行源)的用户列表加入到日志表(d
行源)中的一组日期。请注意连接谓词(ON 子句)中出现的文字“7”,这就是使用户列表与前 6 天“匹配”的原因。
请注意,这也可以扩展以获取过去 3 天的不同用户计数,例如,通过在 SELECT 列表中添加另一个表达式。
, COUNT(DISTINCT IF(u.day<=d.day AND u.day>d.day-3,u.user_id,NULL)) AS 3day
文字“7”可以增加以获得更大的范围。上面表达式中的文字 3 可以更改为任意天数......我们只需要确保我们有足够的前一天行(来自d
)加入到来自u
的每一行。
性能说明:由于内联视图(或 mysql 所称的派生表),此查询可能不会很快,因为这些内联视图的结果集必须具体化到中间 MyISAM 表中。
别名为u
的内联视图可能不是最佳的;直接加入日志表可能会更快。我正在考虑获取给定日期的唯一用户列表,这就是内联视图中的查询让我得到的。我更容易概念化正在发生的事情。而且我在想,如果一天中有数百个相同的用户输入,那么在我们加入其他日子之前,内联视图会清除一大堆重复项。
最好在 u
和 d
内联视图中添加 WHERE 子句来限制我们返回的天数。 (d
内联视图需要包含额外的前 6 天。)
另一方面,如果 ts 列是 TIMESTAMP 数据类型,我会更倾向于使用 DATE(ts)
表达式来提取日期部分。但这会在结果集中返回 DATE 数据类型,而不是整数,这与您指定的结果集不同。)
SELECT d.day
, COUNT(DISTINCT u.user_id) AS wau
, COUNT(DISTINCT IF(u.day=d.day,u.user_id,NULL)) AS dau
FROM ( SELECT DATE(k.ts) AS `day`
FROM `log` k
GROUP BY `day`
) d
JOIN ( SELECT DATE(l.ts) AS `day`
, l.user_id
FROM `log` l
GROUP BY `day`, l.user_id
) u
ON u.day <= d.day
AND u.day > DATE_ADD(d.day, INTERVAL -7 DAY)
GROUP BY d.day
ORDER BY d.day
【讨论】:
感谢您提供非常完整的答案。 ts 是一个大整数。无论效率如何,第一个查询都能完美运行(现在已经足够高效了)。【参考方案2】:这是另一个很好的例子,说明为什么应该使用日期、日期时间或时间戳字段类型来表示数据库中的时间值而不是 unix 时间戳。总是有人想要实际查询该字段,然后您不得不进行一堆时间戳转换,因为整数时间戳值没有时间段的固有概念,您需要根据时间段进行查询。在此过程中,您将失去任何利用字段索引的能力。
无论如何,这是一个非常复杂的查询。可能有比我建议的更好的方法,但希望我的建议至少是有意义的。在这种方法中,您将通过将表连接到自身来执行笛卡尔连接。然后,您可以使用ON
条件限制记录数,以确保第二个日志表中的日期在第一个日志表中日期的 7 天期限内。最后,您进行聚合和分组。查询可能如下所示:
SELECT DATE(FROM_UNIXTIME(log1.ts)) as `day`, COUNT(DISTINCT log2.user_id) as `dau`
FROM log AS log1
INNER JOIN log AS log2
ON DATE(FROM_UNIXTIME(log2.ts)) <= DATE(FROM_UNIXTIME(log1.ts))
AND DATE(FROM_UNIXTIME(log2.ts)) >= DATE_SUB(DATE(FROM_UNIXTIME(log1.ts)), INTERVAL 7 DAY)
GROUP BY `day`
ORDER BY `day` ASC
虽然是一个警告。如果您有相当数量的日志条目,则此查询将需要很长时间才能运行,因为您将结果集中的记录数乘以某个因子,并且您将不会使用索引。
您最好的选择可能是在表中实际创建一个新的日期格式列并运行更新以填充该值。确保您在该字段上有一个索引。那么您的查询可能如下所示:
SELECT log1.date_field as `day`, COUNT(DISTINCT log2.date_field) as `dau`
FROM log AS log1
INNER JOIN log AS log2
ON log2.date_field <= log1.date_field
AND log2.date_field >= DATE_SUB(log1.date_field, INTERVAL 7 DAY)
GROUP BY `day`
ORDER BY `day` ASC
然后您可以在以后的所有日志条目中填充此字段。
【讨论】:
感谢您对日期和时间存储的教育,这对我来说是个新闻。恐怕我目前无法更改时间戳列的类型。您的第一个查询无法正常工作 - 某些列的结果是错误的,尽管我不太清楚为什么。您可以查看 spencer 对返回我需要的结果的查询的回答。还是谢谢!【参考方案3】:这很简单,可以直接获取整周活跃的用户:
选择 yearweek(ts) 作为 yearwk, user_id, 计数(user_id)作为weeklyactiveusers 从日志 按 1,2 分组 有 count(user_id) =7;
【讨论】:
以上是关于日志中每天的每周活跃用户的主要内容,如果未能解决你的问题,请参考以下文章