每组的第一行

时间:2016-09-05 10:18:40

标签: google-bigquery

我有一个表,包含每次访问端点的行。表看起来像这样:

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

3 个答案:

答案 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()。