在 BigQuery 中循环数据

Loop over data in BigQuery

我们一直在努力循环访问(标准 sql)BigQuery 中的数据,但没有成功。

我不确定它是否是 sql 支持的功能、我们对问题的理解或我们想要在 BigQuery 中执行此操作的方式。

无论如何,假设我们有 table 个事件,其中每个事件都由用户 ID 和日期描述(同一用户 ID 在同一日期可能有很多事件)

id  STRING
dt  DATE

我们想知道的一件事是在给定时间段内有多少不同的用户生成了事件。这是相当微不足道的,只是 table 上的一个 COUNT,在 WHERE 子句中以句点作为约束。例如,如果我们有四个月作为时间段:

SELECT
  COUNT(DISTINCT id) AS total
FROM
  `events`
WHERE
  dt BETWEEN DATE_ADD(CURRENT_DATE(), INTERVAL -4 MONTH)
  AND CURRENT_DATE()

但是,如果我们想要在相同的给定时间段内递归地获取其他几天(或几周)的历史记录,我们的问题就会出现。比如昨天、前天等等……直到……比如3个月前。所以这里的变量将是 CURRENT_DATE() ,它可以追溯到一天或任何一个因素,但间隔保持不变(在我们的例子中,4 个月)。我们期待这样的事情(一天的因素):

2017-07-14 2017-03-14 1760333
2017-07-13 2017-03-13 1856333
2017-07-12 2017-03-12 2031993
...
2017-04-14 2017-01-14 1999352

这只是在同一 table 上的每一天、每一周等的循环,然后是对该时间段内发生的不同事件的计数。但是我们不能在 BigQuery 中做 'loops'。

我们认为的一种方法是 JOIN,然后是 GROUP BY 间隔上的 COUNT(利用 HAVING 子句来模拟从给定日期回溯到 4 个月的时间段),但这非常低效并且它table 的大小(它有大约 2.54 亿条记录,截至今天为 173 GB,而且每天都在增长)。

我们想到的另一种方法是使用 UDF,我们将日期间隔列表提供给函数,然后我们的函数将为每个返回间隔和计数的间隔应用朴素查询(用于计数)间隔。但是... BigQuery 中的 UDF 不支持访问 UDF 中的 table,因此我们不得不将整个 table 提供给我们尚未尝试但似乎不合理的 UDF .

因此,我们没有考虑在 BigQuery 中基本上迭代相同数据并对部分数据(如您所见的重叠部分)进行计算的解决方案,我们唯一的解决方案是在 BigQuery 之外执行此操作(循环功能最后)。

有没有办法或有人可以想办法在 BigQuery 中完成所有这些工作?我们的目标是将其作为 BigQuery 内部的视图提供,这样它就不会依赖于需要以我们设置的频率触发的外部系统 (days/weeks/etc...)。

下面是适用于 BigQuery 标准的此技术的示例 SQL

#standardSQL
SELECT 
  DAY,
  COUNT(CASE WHEN period = 7  THEN id END) AS days_07,
  COUNT(CASE WHEN period = 14 THEN id END) AS days_14,
  COUNT(CASE WHEN period = 30 THEN id END) AS days_30
FROM (
  SELECT
    dates.day AS DAY,
    periods.period AS period,
    id
  FROM yourTable AS activity
  CROSS JOIN (SELECT DAY FROM yourTable GROUP BY DAY) AS dates
  CROSS JOIN (SELECT period FROM (SELECT 7 AS period UNION ALL 
                SELECT 14 AS period UNION ALL SELECT 30 AS period)) AS periods
  WHERE dates.day >= activity.day 
  AND CAST(DATE_DIFF(dates.day, activity.day, DAY) / periods.period AS INT64) = 0
  GROUP BY 1,2,3
)
GROUP BY DAY
-- ORDER BY DAY 

您可以 play/test 在此示例中使用如下虚拟数据

#standardSQL
WITH data AS (
  SELECT 
    DAY, CAST(10 * RAND() AS INT64) AS id
  FROM UNNEST(GENERATE_DATE_ARRAY('2017-01-01', '2017-07-13')) AS DAY
)
SELECT 
  DAY,
  COUNT(DISTINCT CASE WHEN period = 7  THEN id END) AS days_07,
  COUNT(DISTINCT CASE WHEN period = 14 THEN id END) AS days_14,
  COUNT(DISTINCT CASE WHEN period = 30 THEN id END) AS days_30
FROM (
  SELECT
    dates.day AS DAY,
    periods.period AS period,
    id
  FROM data AS activity
  CROSS JOIN (SELECT DAY FROM data GROUP BY DAY) AS dates
  CROSS JOIN (SELECT period FROM (SELECT 7 AS period UNION ALL 
                SELECT 14 AS period UNION ALL SELECT 30 AS period)) AS periods
  WHERE dates.day >= activity.day 
  AND CAST(DATE_DIFF(dates.day, activity.day, DAY) / periods.period AS INT64) = 0
  GROUP BY 1,2,3
)
GROUP BY DAY
ORDER BY DAY    

对你有用吗?

WITH dates AS(
  SELECT GENERATE_DATE_ARRAY(DATE_SUB(CURRENT_DATE(), INTERVAL 4 MONTH), CURRENT_DATE()) arr_dates
),
data AS(
  SELECT 1 id, '2017-03-14' dt UNION ALL
  SELECT 1 id, '2017-03-14' dt UNION ALL
  SELECT 1, '2017-04-20' UNION ALL
  SELECT 2, '2017-04-20' UNION ALL
  SELECT 3, '2017-03-15' UNION ALL
  SELECT 4, '2017-04-20' UNION ALL
  SELECT 5, '2017-07-14'
)

SELECT
  i_date date,
  DATE_ADD(i_date, INTERVAL 4 MONTH) next_date,
  (SELECT COUNT(DISTINCT id) FROM data WHERE PARSE_DATE("%Y-%m-%d", data.dt) BETWEEN i_date AND DATE_ADD(i_date, INTERVAL 4 MONTH)) total
FROM dates,
UNNEST(arr_dates) i_date
ORDER BY i_date

其中 data 是您的 events table 的模拟。