我有一个表,包含每次访问端点的行。表看起来像这样:
user_id STRING
endpoint_id STRING
created_at TIMESTAMP
示例数据:
user-1, endpoint-1, 2016-01-01 01:01:01 UTC
user-2, endpoint-1, 2016-01-01 01:01:01 UTC
user-1, endpoint-2, 2016-01-02 01:01:01 UTC
user-1, endpoint-1, 2016-01-02 01:01:01 UTC
user-1, endpoint-1, 2016-01-03 01:01:01 UTC
如何为每个用户和资源获取第一次访问行。
构建此类查询的最佳方法是什么?
预期结果:
user-1, endpoint-1, 2016-01-01 01:01:01 UTC
user-2, endpoint-1, 2016-01-01 01:01:01 UTC
user-1, endpoint-2, 2016-01-02 01:01:01 UTC
这是我提出的问题,但此查询不适用于大量数据。我使用窗口函数将重复用户/资源行组合在一起:
SELECT
user_id,
endpoint_id,
created_at
FROM (
SELECT
poll_id,
endpoint_id,
created_at,
FIRST_VALUE(created_at) OVER (PARTITION BY user_id, endpoint_id ORDER BY created_at DESC) AS first_created_at
FROM
[visits]
)
WHERE
created_at = first_created_at
答案 0 :(得分:7)
如何获得每个用户和资源的第一次访问行?
在您提出问题的查询中 - 应删除DESC
中的ORDER BY created_at DESC
,否则会返回上次访问 - 而不是第一次访问
构建此类查询的最佳方法是什么?
另一种选择是使用ROW_NUMBER(),如下所示
SELECT
user_id,
endpoint_id,
created_at
FROM (
SELECT
user_id,
endpoint_id,
created_at,
ROW_NUMBER() OVER(PARTITION BY user_id, endpoint_id ORDER BY created_at) AS first_created
FROM [visits]
)
WHERE first_created = 1
...但此查询不适用于大量数据
这实际上取决于。如果Resources Exceeded
分区的大小足够大(因为ORDER BY要求所有分区行都在同一节点上),可能会发生user_id, endpoint_id
。
如果是这种情况,您可以在
下方使用trick
第1步 - 使用JOIN
SELECT tab1.user_id AS user_id, tab1.endpoint_id AS endpoint_id, tab1.created_at AS created_at
FROM [visits] AS tab1
INNER JOIN (
SELECT user_id, endpoint_id, MIN(created_at) AS min_time
FROM [visits]
GROUP BY user_id, endpoint_id
) AS tab2
ON tab1.user_id = tab2.user_id
AND tab1.endpoint_id = tab2.endpoint_id
AND tab1.created_at = tab2.min_time
步骤2 - 此处还有其他需要注意的事项 - 如果您有相同用户/资源的重复条目。在这种情况下,您仍然需要为每个分区仅提取一行。见下面的最终查询
SELECT user_id, endpoint_id, created_at
FROM (
SELECT user_id, endpoint_id, created_at,
ROW_NUMBER() OVER (PARTITION BY user_id, endpoint_id) AS rn
FROM (
SELECT tab1.user_id AS user_id, tab1.endpoint_id AS endpoint_id, tab1.created_at AS created_at
FROM [visits] AS tab1
INNER JOIN (
SELECT user_id, endpoint_id, MIN(created_at) AS min_time
FROM [visits]
GROUP BY user_id, endpoint_id
) AS tab2
ON tab1.user_id = tab2.user_id
AND tab1.endpoint_id = tab2.endpoint_id
AND tab1.created_at = tab2.min_time
)
)
WHERE rn = 1
当然是明显和最简单的案例 - 如果这三个领域是 [visits]表中的唯一字段
SELECT user_id, endpoint_id, MIN(created_at) AS created_at
FROM [visits]
GROUP BY user_id, endpoint_id
答案 1 :(得分:1)
您现在可以使用 qualify
来获得更简洁的解决方案:
select
user_id,
endpoint_id,
created_at,
from [visits]
where true
qualify ROW_NUMBER() OVER(PARTITION BY user_id, endpoint_id ORDER BY created_at) = 1
答案 2 :(得分:0)
我还有另一种避免使用窗口函数(在BQ中我认为v慢)和子查询(这会增加复杂性)的解决方案:
select
group_column
,array_agg(struct(column_1,column_2) order by time_column asc limit 1)[offset(0)] AS first_row
from table
group by 1
array_agg返回一个数组,其中每个组的第一行的结构分别为column_1和column_2。这是使用[offset(0)]从数组中提取的。您可以使用first_row.column_1从结构中进一步提取。或者,您可以避免使用struct()并使用多个array_agg()。