工作中经常碰到需要按固定时间间隔补齐时间轴的需求,比如按分钟、小时、天来生成连续的记录,然后对缺失的时间点补 0 或空值。这里整理了一份通用做法合集,覆盖 PostgreSQL、MySQL、SQL Server、SQLite、BigQuery、Snowflake 等主流数据库。思路其实就三步:先生成
工作中经常碰到需要按固定时间间隔补齐时间轴的需求,比如按分钟、小时、天来生成连续的记录,然后对缺失的时间点补 0 或空值。这里整理了一份通用做法合集,覆盖 PostgreSQL、MySQL、SQL Server、SQLite、BigQuery、Snowflake 等主流数据库。思路其实就三步:先生成连续时间序列,再左连接原数据,最后做聚合统计。

长期稳定更新的攒劲资源: >>>点此立即查看<<<
PostgreSQL 原生有 generate_series,写法非常优雅。
示例:每5分钟补齐一次,统计每5分钟事件数
WITH ts AS ( SELECT generate_series( timestamp '2025-10-01 00:00:00', timestamp '2025-10-02 00:00:00', interval '5 minute' ) AS bucket),events AS ( SELECT date_trunc('minute', event_time) AS minute_ts FROM public.event_log WHERE event_time >= '2025-10-01 00:00:00' AND event_time < '2025-10-02 00:00:00')SELECT ts.bucket, COALESCE(cnt.c, 0) AS event_countFROM tsLEFT JOIN ( SELECT date_trunc('minute', minute_ts) - (extract(minute FROM minute_ts)::int % 5) * interval '1 minute' AS bucket_5m, count(*) AS c FROM events GROUP BY 1) cntON ts.bucket = cnt.bucket_5mORDER BY ts.bucket;
如果你的数据已经是整分,就可以把上面的“对5分钟分箱”的表达式简化成
date_trunc('minute', event_time)再对interval '5 minute'进行对齐。
每天/每小时序列只需把 interval '5 minute' 换成 interval '1 hour' 或 interval '1 day',同时聚合逻辑改为 date_trunc('hour'/'day', ...)。
MySQL 没有内置的 generate_series,我们用递归CTE造序列。
示例:每15分钟补齐一次
WITH RECURSIVE ts AS ( SELECT TIMESTAMP('2025-10-01 00:00:00') AS bucket UNION ALL SELECT bucket + INTERVAL 15 MINUTE FROM ts WHERE bucket < '2025-10-02 00:00:00'),events AS ( SELECT event_time FROM event_log WHERE event_time >= '2025-10-01 00:00:00' AND event_time < '2025-10-02 00:00:00'),agg AS ( SELECT -- 把 event_time 对齐到 15分钟的时间桶 FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(event_time) / (15*60)) * (15*60)) AS bucket_15m, COUNT(*) AS c FROM events GROUP BY 1)SELECT ts.bucket, COALESCE(agg.c, 0) AS event_countFROM tsLEFT JOIN agg ON ts.bucket = agg.bucket_15mORDER BY ts.bucketOPTION MAX_RECURSION_DEPTH = 100000; -- 如有需要可调整
性能提示:长时间跨度建议用“辅助数字表/日历表/时间维表”替代递归;或者先生成按天的序列再在应用层扩展。
WITH ts AS ( SELECT CAST('2025-10-01T00:00:00' AS datetime2) AS bucket UNION ALL SELECT DATEADD(minute, 10, bucket) FROM ts WHERE bucket < CAST('2025-10-02T00:00:00' AS datetime2)),agg AS ( SELECT DATEADD(minute, DATEDIFF(minute, 0, event_time) / 10 * 10, 0) AS bucket_10m, COUNT(*) AS c FROM dbo.EventLog WHERE event_time >= '2025-10-01T00:00:00' AND event_time < '2025-10-02T00:00:00' GROUP BY DATEADD(minute, DATEDIFF(minute, 0, event_time) / 10 * 10, 0))SELECT ts.bucket, ISNULL(agg.c, 0) AS event_countFROM tsLEFT JOIN agg ON ts.bucket = agg.bucket_10mORDER BY ts.bucketOPTION (MAXRECURSION 0);
先准备一个连续整数表(可持久化)。随后:
DECLARE @start datetime2 = '2025-10-01T00:00:00';DECLARE @end datetime2 = '2025-10-02T00:00:00';WITH ts AS ( SELECT DATEADD(minute, n*5, @start) AS bucket FROM dbo.Numbers WHERE DATEADD(minute, n*5, @start) <= @end)-- 其余与上面 LEFT JOIN 聚合同理
WITH RECURSIVE ts(bucket) AS ( SELECT DATETIME('2025-10-01 00:00:00') UNION ALL SELECT DATETIME(bucket, '+5 minutes') FROM ts WHERE bucket < '2025-10-02 00:00:00'),agg AS ( SELECT DATETIME(STRFTIME('%s', event_time) / (5*60) * (5*60), 'unixepoch') AS bucket_5m, COUNT(*) AS c FROM event_log WHERE event_time >= '2025-10-01 00:00:00' AND event_time < '2025-10-02 00:00:00' GROUP BY 1)SELECT ts.bucket, IFNULL(agg.c, 0) AS event_countFROM tsLEFT JOIN agg ON ts.bucket = agg.bucket_5mORDER BY ts.bucket;
WITH ts AS ( SELECT ts AS bucket FROM UNNEST( GENERATE_TIMESTAMP_ARRAY( TIMESTAMP('2025-10-01 00:00:00+00'), TIMESTAMP('2025-10-02 00:00:00+00'), INTERVAL 15 MINUTE ) ) AS ts),agg AS ( SELECT TIMESTAMP_TRUNC(event_time, MINUTE) - INTERVAL MOD(EXTRACT(MINUTE FROM event_time), 15) MINUTE AS bucket_15m, COUNT(*) AS c FROM `project.dataset.event_log` WHERE event_time >= TIMESTAMP('2025-10-01 00:00:00+00') AND event_time < TIMESTAMP('2025-10-02 00:00:00+00') GROUP BY 1)SELECT ts.bucket, IFNULL(agg.c, 0) AS event_countFROM tsLEFT JOIN agg ON ts.bucket = agg.bucket_15mORDER BY ts.bucket;
WITH params AS ( SELECT TO_TIMESTAMP('2025-10-01 00:00:00') AS start_ts, TO_TIMESTAMP('2025-10-02 00:00:00') AS end_ts, 5 AS step_min),ts AS ( SELECT DATEADD(minute, seq4()*step_min, start_ts) AS bucket FROM params, TABLE(GENERATOR(ROWCOUNT => 100000000)) -- 上限要能覆盖区间长度 QUALIFY bucket <= (SELECT end_ts FROM params)),agg AS ( SELECT DATE_TRUNC('minute', event_time) - (DATE_PART(minute, event_time) % 5) * INTERVAL '1 minute' AS bucket_5m, COUNT(*) AS c FROM EVENT_LOG WHERE event_time >= (SELECT start_ts FROM params) AND event_time < (SELECT end_ts FROM params) GROUP BY 1)SELECT ts.bucket, COALESCE(agg.c, 0) AS event_countFROM tsLEFT JOIN agg ON ts.bucket = agg.bucket_5mORDER BY ts.bucket;
ROWCOUNT要覆盖足够的时间点:大致 = (总分钟数 / step_min) + 1。
把这段思想搬到任何库都成立:
start_ts、end_ts、step(分钟/小时/天)。step 对齐的“时间桶”。00,05,10,...,55 上。不同数据库对齐写法不同,上面示例已给出。[start, end),避免终点重复。侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述