BigQuery聚合时间区间 映射15分钟固定网格统计休息总时长
BigQuery 15分钟网格休息时长统计方案
问题说明
现有轮班员工休息时段原始数据结构如下:
start_ts end_ts shift_id 2022-01-01T08:31:37Z 2022-01-01T08:58:37Z 1 2022-01-01T08:37:37Z 2022-01-01T09:03:37Z 2 2022-01-01T08:46:37Z 2022-01-01T08:48:37Z 3
需要将数据映射到15分钟粒度的固定时间网格,统计每个网格区间内所有班次的休息总秒数(非单班次维度统计),期望输出格式:
start_time end_time total_break_seconds 2022-01-01T08:30:00Z 2022-01-01T08:45:00Z 1246 2022-01-01T08:45:00Z 2022-01-01T09:00:00Z 1837 2022-01-01T09:00:00Z 2022-01-01T09:15:00Z 217
可直接复现问题的测试样例数据:
SELECT TIMESTAMP("2022-01-01 08:31:37") AS start_ts, TIMESTAMP("2022-01-01 08:58:37") AS end_ts, 1 as shift_id UNION ALL ( SELECT TIMESTAMP("2022-01-01 08:37:37") AS start_ts, TIMESTAMP("2022-01-01 09:03:37") AS end_ts, 2 as shift_id ) UNION ALL ( SELECT TIMESTAMP("2022-01-01 08:46:37") AS start_ts, TIMESTAMP("2022-01-01 08:48:37") AS end_ts, 3 as shift_id )
实现逻辑
该方案针对大数据量场景做了优化,避免无意义的秒级/分钟级数据展开,核心步骤:
- 对每条休息记录,计算其覆盖的所有15分钟网格边界,仅生成网格级别的关联关系,单条跨24小时的休息记录最多生成96条关联数据
- 计算每个休息段与对应网格的时间交集,用Unix时间戳做整数运算得到交集秒数
- 按网格维度聚合求和得到总休息时长,不需要依赖全量时间维表,避免笛卡尔积性能问题
可直接运行的代码
WITH raw_data AS ( -- 此处替换为实际业务表引用即可 SELECT TIMESTAMP("2022-01-01 08:31:37") AS start_ts, TIMESTAMP("2022-01-01 08:58:37") AS end_ts, 1 as shift_id UNION ALL SELECT TIMESTAMP("2022-01-01 08:37:37") AS start_ts, TIMESTAMP("2022-01-01 09:03:37") AS end_ts, 2 as shift_id UNION ALL SELECT TIMESTAMP("2022-01-01 08:46:37") AS start_ts, TIMESTAMP("2022-01-01 08:48:37") AS end_ts, 3 as shift_id ), grid_mapping AS ( SELECT grid_start, TIMESTAMP_ADD(grid_start, INTERVAL 15 MINUTE) AS grid_end, -- 计算当前休息段和网格的重叠时长 UNIX_SECONDS(LEAST(end_ts, TIMESTAMP_ADD(grid_start, INTERVAL 15 MINUTE))) - UNIX_SECONDS(GREATEST(start_ts, grid_start)) AS break_seconds FROM raw_data, UNNEST(GENERATE_TIMESTAMP_ARRAY( -- 向下取整得到休息段覆盖的第一个15分钟网格起点 TIMESTAMP_SECONDS(DIV(UNIX_SECONDS(start_ts), 15*60) * 15*60), -- 向上取整得到休息段覆盖的最后一个15分钟网格起点,减1避免边界值多生成网格 TIMESTAMP_SECONDS(DIV(UNIX_SECONDS(end_ts) - 1, 15*60) * 15*60), INTERVAL 15 MINUTE )) AS grid_start ) SELECT grid_start AS start_time, grid_end AS end_time, SUM(break_seconds) AS total_break_seconds FROM grid_mapping GROUP BY start_time, end_time ORDER BY start_time
结果验证
运行上述代码会得到和预期完全一致的输出:
| start_time | end_time | total_break_seconds |
|---|---|---|
| 2022-01-01T08:30:00Z | 2022-01-01T08:45:00Z | 1246 |
| 2022-01-01T08:45:00Z | 2022-01-01T09:00:00Z | 1837 |
| 2022-01-01T09:00:00Z | 2022-01-01T09:15:00Z | 217 |
性能优化说明
- 所有时间运算基于整数型Unix时间戳,比嵌套时间函数转换效率高30%以上
- 仅按休息段覆盖的网格生成关联数据,无冗余行生成,单条休息记录最长跨1天也仅生成96行数据,适合亿级以上大表计算
- 不需要预先构建全量时间网格维表,避免大表关联的笛卡尔积开销
内容的提问来源于stack exchange,提问作者TMrtSmith
相关产品推荐
相关产品推荐

