有没有办法改变这个 BigQuery 自联接以使用窗口函数?

Posted

技术标签:

【中文标题】有没有办法改变这个 BigQuery 自联接以使用窗口函数?【英文标题】:Is there a way to change this BigQuery self-join to use a window function? 【发布时间】:2019-07-09 12:26:51 【问题描述】:

假设我有一个 BigQuery 表“事件”(实际上这是一个缓慢的子查询),它按事件类型存储每天的事件计数。事件的类型很多,而且大多数都不会在大多数日子发生,因此只有一行非零计数的日期/事件类型组合。

我有一个查询,它返回 N 天前每种事件类型和日期的计数以及该事件的计数,如下所示:

WITH events AS (
  SELECT DATE('2019-06-08') AS day, 'a' AS type, 1 AS count
  UNION ALL SELECT '2019-06-09', 'a', 2
  UNION ALL SELECT '2019-06-10', 'a', 3
  UNION ALL SELECT '2019-06-07', 'b', 4
  UNION ALL SELECT '2019-06-09', 'b', 5
)
SELECT e1.type, e1.day, e1.count, COALESCE(e2.count, 0) AS prev_count
FROM events e1
LEFT JOIN events e2 ON e1.type = e2.type AND e1.day = DATE_ADD(e2.day, INTERVAL 2 DAY) -- LEFT JOIN, because the event may not have occurred at all 2 days ago
ORDER BY 1, 2

查询很慢。 BigQuery best practices 建议使用窗口函数而不是自连接。有没有办法在这里做到这一点?如果每天有一行,我可以使用LAG 函数,但没有。我可以以某种方式“填充”它吗? (没有可能的事件类型的简短列表。我当然可以加入SELECT DISTINCT type FROM events,但这可能不会比自加入更快。)

【问题讨论】:

这有多慢?如果您有数千天和数百个事件,那么您的表仍然很小。我希望查询会相当快。 在实际应用中,它不是一个表,而是一个子查询。编辑问题来说明这一点。 (我试图保持简单)。整个查询大约需要 5-6 秒。 【参考方案1】:

蛮力方法是:

select e.*,
       (case when lag(day) over (partition by type order by date) = dateadd(e.day, interval -2 day)
             then lag(cnt) over (partition by type order by date)
             when lag(day, 2) over (partition by type order by date) = dateadd(e.day, interval -2 day)
             then lag(cnt, 2) over (partition by type order by date)
        end) as prev_day2_count
from events e;

这可以延迟两天。延迟时间越长越麻烦。

编辑:

更通用的表单使用窗框。不幸的是,这些必须是数字,所以还有一个额外的步骤:

select e.*,
       (case when min(day) over (partition by type order by diff range between 2 preceding and current day) = date_add(day, interval -2 day)
             then first_value(cnt) over (partition by type order by diff range between 2 preceding and current day)
        end)
from (select e.*,
             date_diff(day, max(day) over (partition by type), day) as diff   -- day is a bad name for a column because it is a date part
      from events e
     ) e;

还有啊! case 表达式不是必需的:

select e.*,
       first_value(cnt) over (partition by type order by diff range between 2 preceding and 2 preceding)
from (select e.*,
             date_diff(day, max(day) over (partition by type), day) as diff   -- day is a bad name for a column because it is a date part
      from events e
     ) e;

【讨论】:

这实际上并不算太糟糕,延迟长达一周左右,稍作修改(请参阅已编辑的问题)。它比真实查询的自连接略快,所以我正在使用它。谢谢! @EM0 。 . .最终版本应该是最好的解决方案。 太好了,非常感谢!它显示了 2 天 的值,所以我不得不将 preceding 更改为 followingmax(day) 似乎也没有必要——它的目的只是将日期转换为整数,所以它可以是与固定日期的差异,对吧?我用我最终使用的查询更新了问题。 @EM0 。 . . max(day) 不是必需的。您可以使用固定日期。参数的顺序是diff 为正数。 我必须 date_diff(day, '1970-01-01', day) 使差异为正,然后 range between 2 preceding 工作。这可能更易于阅读,因此我将其更改为。【参考方案2】:

以下是 BigQuery 标准 SQL

#standardSQL
SELECT *, IFNULL(FIRST_VALUE(count) OVER (win), 0) prev_count
FROM `project.dataset.events`
WINDOW win AS (PARTITION BY type ORDER BY UNIX_DATE(day) RANGE BETWEEN 2 PRECEDING AND 2 PRECEDING)   

如果 t 适用于您问题的样本数据 - 结果是:

Row day         type    count   prev_count   
1   2019-06-08  a       1       0    
2   2019-06-09  a       2       0    
3   2019-06-10  a       3       1    
4   2019-06-07  b       4       0    
5   2019-06-09  b       5       4    

【讨论】:

以上是关于有没有办法改变这个 BigQuery 自联接以使用窗口函数?的主要内容,如果未能解决你的问题,请参考以下文章

SQL自联命名?

有没有办法使用 BigQuery 视图作为数据流的输入?

有没有办法使用预编译的 sql 完成工作并通过 java api (bigquery) 多次运行

有没有办法直接使用链接在 RStudio 中运行保存的 bigquery? [复制]

有没有办法在 BigQuery 中保存的视图中使用脚本方法?

有没有办法使用 Google 应用程序脚本从 Bigquery 结果生成 Microsoft Excel?