2012-12-14 28 views
0

我想知道是否有人可以帮助我用一些SQL来返回在两天或更长时间内登录到数据库表中的唯一用户数量(让我们使用7天作为参考)。每日活跃用户每日从日志

我的日志表在每一行中包含一个时间戳(ts)和user_id,表示当时该用户的活动。

SELECT FLOOR(ts/86400) AS day, COUNT(DISTINCT user_id) AS dau 
FROM log 
GROUP BY day ORDER BY day ASC 

现在让我们说,我想加入到这个单一查询(或以最有效的可能方式至少检索)的:

以下查询此日志返回每日活跃用户或DAU每周活动用户或总唯一用户记录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) 

感谢您的帮助,您可以提供,并随时通过评论问,如果你需要进一步澄清。

回答

3

要获得每周的“每周平均用户数”(根据我对您的规范的理解......),每天看到的不同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 rowsource)到用户列表到日志表(d行源)的一组日期。请注意出现在连接谓词中的文字“7”( ON条款),这是什么让用户列表“匹配”到前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的内联视图可能不是最优的;直接加入日志表可能会更快。我正在考虑在特定的一天中获取用户的唯一列表,这是内联视图中的查询为我提供的内容。对我来说,构想发生的事情更容易。我在想,如果你有几百个用户输入了一天,内联视图会在我们加入其他日子之前清除一大堆重复项。 在ud内嵌视图中,最好将WHERE子句限制我们返回的天数。(该d联视图将需要包括额外的较早6天。)


在另一方面,如果TS列timestamp数据类型,我会更倾向于使用一个DATE(ts)表达式提取的日期部分。但是,这将在结果集中返回一个日期数据类型,而不是一个整数,这将是从你指定的结果集不同。)

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 

+0

谢谢你提供一个非常完整的答案。 ts是一个bigint。第一个查询完美无缺,无论效率如何(目前效率足够高)。 – Protected

2

这里是为什么要使用日期,日期时间的另一个很好的例子或时间戳字段类型来表示数据库中的时间值而不是unix时间戳。总而言之,有人希望实际查询该字段,然后必须执行一堆时间戳转换,因为整数时间戳值没有时间周期的固有概念,需要根据时间段进行查询。在这个过程中,你失去了在领域使用索引的能力。

无论如何,这是一个非常复杂的查询,你正在寻找。可能有比我所建议的更好的方法,但希望我的建议至少有意义。在这种方法中,您可以通过将表加入到自己来执行笛卡尔连接。然后通过使用ON条件来限制记录数,以确保第二个日志表中的日期在第一个日志表中的日期的七天内。最后,你做你的聚合和分组。查询可能如下所示:

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 

然后,您可以在前进的所有日志条目中填写此字段。

+0

感谢您对日期和时间存储的教育,这是我的新闻。恐怕我现在不能改变时间戳列的类型。你的第一个查询不能正常工作 - 某些列的结果是错误的,尽管我不太清楚为什么。您可以查看spencer的答案,查询返回我需要的结果。不管怎么说,还是要谢谢你! – Protected

0

这是简单而直接获得谁是全周活跃用户:1从日志 组weeklyactiveusers

选择年周(TS)为yearwk,USER_ID, 计数(USER_ID) 2 having count(user_id)= 7;