2

我想知道是否有人可以帮助我使用一些 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)

感谢您提供的任何帮助,如果您需要进一步说明,请随时通过评论询问。

4

3 回答 3

5

要获得“每周平均用户”计数(根据我对您的规范的理解......“对于每一天,当天和前六天看到的不同 user_ids 的计数”),请按照以下行进行查询可用于。(查询还返回“每日平均用户”计数。

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 表中。

别名为 as 的内联视图u可能不是最佳的;直接加入日志表可能会更快。我正在考虑获取给定日期的唯一用户列表,这就是内联视图中的查询让我得到的。我更容易概念化正在发生的事情。而且我在想,如果一天有数百个相同的用户输入,那么在我们加入其他日子之前,内联视图会清除一大堆重复项。一个 WHERE 子句来限制我们返回的天数最好添加到ud联视图中。(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

于 2012-12-14T19:39:52.560 回答
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

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

于 2012-12-14T19:48:42.160 回答
0

获取整周活跃用户的方法简单明了:

从日志组中选择 yearweek(ts) 作为 yearwk,user_id,count(user_id) 作为weeklyactiveusers 由 1,2 具有 count(user_id) =7;

于 2017-04-13T04:27:38.903 回答