【问题标题】:Loop over data in BigQuery在 BigQuery 中循环数据
【发布时间】:2017-12-19 11:30:42
【问题描述】:

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

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

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

id  STRING
dt  DATE

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

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

这只是对同一张表上的每一天、每一周等进行循环,然后对该时间段内发生的不同事件进行计数。但我们不能在 BigQuery 中执行“循环”。

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

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

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

有没有办法或有人可以想办法在 BigQuery 中完成这一切?我们的目标是将其作为 BigQuery 内部的视图提供,以便它不依赖于需要以我们设置的频率(天/周/等)触发的外部系统。

【问题讨论】:

  • SQL 没有循环。您应该能够使用分析函数,即 OVER (PARTITION)RANGE cloud.google.com/bigquery/docs/reference/standard-sql/…。开始 -> stackoverflow.com/questions/29899097/…
  • GENERATE_DATE_ARRAY 应该让您在日期上创建一个“循环”。除非其他人先添加答案,否则我稍后会尝试添加答案。
  • @ElliottBrossard - 为时已晚。米哈伊尔打败了你 ;-) 感谢你把我指向 GENERATE_DATE_ARRAY - 从来不知道它存在!
  • @GrahamPolley,当然非常清楚这一点,我们只是试图通过与自身交叉连接来模拟它。但正是交叉连接带来了麻烦(我想是性能方面的),而一种聪明的做法是我们还没有弄清楚,我也没有找到很多例子。正如@ElliottBrossard 指出的那样,GENERATE_DATE_ARRAY 似乎是可能的,但是在尝试了下面@Will 的建议后,我们得到了我作为评论指出的错误(并且寻找该错误表明解决问题的另一种方法)。

标签: google-bigquery user-defined-functions


【解决方案1】:

以下是 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 

您可以使用下面的虚拟数据来玩/测试这个示例

#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    

【讨论】:

  • 非常感谢!这似乎是我们主要的目标(而且很快……非常快!)。我现在了解 BigQuery 的强大功能,尽管用户做了一些工作以便 BQ 也可以轻松处理它,而且我看到了您正在使用的技术。然而,我们很难理解 WHERE 子句的第二个约束,实际上是除法。由于强制转换,除法的结果被四舍五入(向上或向下),然后我们错过了与零进行比较的目标(我将其解释为周期的模数,以便将条目分配给正确的周期)。它有什么用?
【解决方案2】:

它对你有用吗?

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 表的模拟。

【讨论】:

  • 哈!聪明又整洁!我看到你试图模仿“循环”的方式!我们试过了,但是当我们用我们的表替换 data 表时,我们得到一个“臭名昭著”的错误:LEFT OUTER JOIN cannot be used without a condition that is an equality of fields from both sides of the join.在谷歌搜索后似乎带来了一些与我们类似的问题,但除了接近之外,没有明确的答案来解决以其他方式解决问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-16
相关资源
最近更新 更多