【问题标题】:Rolling 90 days active users in BigQuery, improving preformance (DAU/MAU/WAU)在 BigQuery 中滚动 90 天活跃用户,提高性能 (DAU/MAU/WAU)
【发布时间】:2018-09-25 21:37:09
【问题描述】:

我正在尝试获取特定日期的唯一事件数,回滚 90/30/7 天。我已经使用下面的查询在有限数量的行上进行了这项工作,但是对于大型数据集,我会从变得庞大的聚合字符串中得到内存错误。

我正在寻找一种更有效的方法来实现相同的结果。

表格看起来像这样:

+---+------------+-------------+
|   |     date   |     userid  |
+---+------------+-------------+
| 1 | 2013-05-14 | xxxxx       |
| 2 | 2017-03-14 | xxxxx       |
| 3 | 2018-01-24 | xxxxx       |
| 4 | 2013-03-21 | xxxxx       |
| 5 | 2014-03-19 | xxxxx       |
| 6 | 2015-09-03 | xxxxx       |
| 7 | 2014-02-06 | xxxxx       |
| 8 | 2014-10-30 | xxxxx       |
| ..| ...        | ...         |
+---+------------+-------------+

所需结果的格式:

+---+------------+---------------------------------------------+
|   |     date   | active_users_7_days | active_users_90_days  |
+---+------------+---------------------------------------------+
| 1 | 2013-05-14 | 1240                | 34339                 |
| 2 | 2017-03-14 | 4334                | 54343                 |
| 3 | 2018-01-24 | .....               | .....                 |
| 4 | 2013-03-21 | .....               | .....                 |
| 5 | 2014-03-19 | .....               | .....                 |
| 6 | 2015-09-03 | .....               | .....                 |
| 7 | 2014-02-06 | .....               | .....                 |
| 8 | 2014-10-30 | .....               | .....                 |
| ..| ...        | .....               | .....                 |
+---+------------+---------------------------------------------+

我的查询如下所示:

#standardSQL
    WITH
      T1 AS(
      SELECT
        date,
        STRING_AGG(DISTINCT userid) AS IDs
      FROM
        `consumer.events`
      GROUP BY
        date ),
      T2 AS(
      SELECT
        date,
        STRING_AGG(IDs) OVER(ORDER BY UNIX_DATE(date) RANGE BETWEEN 90 PRECEDING
          AND CURRENT ROW) AS IDs
      FROM
        T1 )
    SELECT
      date,
      (
      SELECT
        COUNT(DISTINCT (userid))
      FROM
        UNNEST(SPLIT(IDs)) AS userid) AS NinetyDays
    FROM
      T2

【问题讨论】:

  • 你为什么想要一个巨大的STRING_AGG(DISTINCT userid)
  • @FelipeHoffa 我想我需要按日期分组的不同用户 ID。您还有其他更有效的方法来实现结果吗?
  • @Frithiof 我认为 Felipe 的问题是您需要显示实际的 id 还是仅计算一个就足够了?导致内存错误的是字符串的聚合,除非您确实需要查看它们,然后只需返回一个计数。
  • @BenP 是的,字符串的聚合导致了错误。我不需要查看实际的 ID。也许我的速度很慢,但是如何在不聚合它们的情况下计算日期范围内的不同 ID?

标签: sql google-bigquery bigquery-standard-sql


【解决方案1】:

计算唯一身份用户需要大量资源,如果您希望通过滚动窗口获得结果,则需要更多资源。对于可扩展的解决方案,请查看 HLL++ 等近似算法:

对于精确计数,这会起作用(但随着窗口变大会变慢):

#standardSQL
SELECT DATE_SUB(date, INTERVAL i DAY) date_grp
 , COUNT(DISTINCT owner_user_id) unique_90_day_users
 , COUNT(DISTINCT IF(i<31,owner_user_id,null)) unique_30_day_users
 , COUNT(DISTINCT IF(i<8,owner_user_id,null)) unique_7_day_users
FROM (
  SELECT DATE(creation_date) date, owner_user_id
  FROM `bigquery-public-data.stackoverflow.posts_questions` 
  WHERE EXTRACT(YEAR FROM creation_date)=2017
  GROUP BY 1, 2
), UNNEST(GENERATE_ARRAY(1, 90)) i
GROUP BY 1
ORDER BY date_grp

近似解决方案产生结果的速度更快(14s vs 366s,但结果是近似的):

#standardSQL
SELECT DATE_SUB(date, INTERVAL i DAY) date_grp
 , HLL_COUNT.MERGE(sketch) unique_90_day_users
 , HLL_COUNT.MERGE(DISTINCT IF(i<31,sketch,null)) unique_30_day_users
 , HLL_COUNT.MERGE(DISTINCT IF(i<8,sketch,null)) unique_7_day_users
FROM (
  SELECT DATE(creation_date) date, HLL_COUNT.INIT(owner_user_id) sketch
  FROM `bigquery-public-data.stackoverflow.posts_questions` 
  WHERE EXTRACT(YEAR FROM creation_date)=2017
  GROUP BY 1
), UNNEST(GENERATE_ARRAY(1, 90)) i
GROUP BY 1
ORDER BY date_grp


更新的查询提供了正确的结果 - 删除少于 90 天的行(在没有日期缺失的情况下有效):

#standardSQL
SELECT DATE_SUB(date, INTERVAL i DAY) date_grp
 , HLL_COUNT.MERGE(sketch) unique_90_day_users
 , HLL_COUNT.MERGE(DISTINCT IF(i<31,sketch,null)) unique_30_day_users
 , HLL_COUNT.MERGE(DISTINCT IF(i<8,sketch,null)) unique_7_day_users
 , COUNT(*) window_days
FROM (
  SELECT DATE(creation_date) date, HLL_COUNT.INIT(owner_user_id) sketch
  FROM `bigquery-public-data.stackoverflow.posts_questions` 
  WHERE EXTRACT(YEAR FROM creation_date)=2017
  GROUP BY 1
), UNNEST(GENERATE_ARRAY(1, 90)) i
GROUP BY 1
HAVING window_days=90
ORDER BY date_grp

【讨论】:

  • 谢谢你,Felipe,这就是我要找的。也会研究 HLL++。
  • 然而,在整个日期范围的前 90 天和最后 90 天,结果将不准确。我的查询会更正确,因为它每行回滚 90 天。有什么办法可以做到这一点?
  • 简单:删除前 90 天的结果,或将范围扩大 90 天。
  • 如果我错了,请纠正我,但如果我将范围扩大 90 天,我将填充未来的日期,这没有任何意义。我会为此使用 LIMIT 和 OFFSET 吗?
  • 要更新结果,请使用DATE_ADD 而不是DATE_SUB。它为您提供完全相同的结果,不同之处在于它列出了 90 天期间的最后日期,而不是第一天。但无论如何,您都会获得最新的 90 天。
【解决方案2】:

您可以汇总日期并求和。什么是聚合?取最近的日期:

select count(*) as num_users,
       sum(case when date > datediff(current_date, interval -30 day) then 1 else 0 end) as num_users_30days,
       sum(case when date > datediff(current_date, interval -60 day) then 1 else 0 end) as num_users_60days,
       sum(case when date > datediff(current_date, interval -90 day) then 1 else 0 end) as num_users_90days
from (select user_id, max(date) as max(date)
      from `consumer.events` e
      group by user_id
     ) e;

如果用户最近的日期在该时间段内,则应计算该用户。

您可以通过在子查询中使用where 子句来获取特定日期的“当前日期”。

【讨论】:

  • 感谢 Gordon,但我需要表格中所有日期的每个日期的结果。我已经用我想要的格式编辑了我的问题。
猜你喜欢
  • 2017-05-26
  • 1970-01-01
  • 2023-01-29
  • 2019-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多