有没有办法改变这个 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
更改为 following
。 max(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 完成工作并通过 java api (bigquery) 多次运行
有没有办法直接使用链接在 RStudio 中运行保存的 bigquery? [复制]