LAG()仅用于检测时间断层,补齐时间轴需先用GENERATE_SERIES生成完整时间点再LEFT JOIN;GENERATE_SERIES参数须显式指定类型和单位,JOIN前需统一截断时间精度;缺口识别后应分段补全而非全量扫描,聚合时用COALESCE确保空值为0。

LAG() 只能检测断层,不能补齐时间轴——窗口函数本身不生成新行,必须配合 GENERATE_SERIES 或递归 CTE 先造出完整时间点,再 LEFT JOIN。
GENERATE_SERIES 是补齐时间轴的起点,不是终点
-
GENERATE_SERIES必须传入明确类型:起止参数得是DATE或TIMESTAMP,写成'2024-01-01'字符串会报错,要写成'2024-01-01'::DATE - 步长必须显式带单位:
'1 day'、'5 minute'、'1 month',漏掉INTERVAL或单位(比如只写'1')会失败 - 如果原始数据含时间戳但你要按天对齐,
JOIN前必须统一截断:date_trunc('day', event_time)或event_time::DATE,否则无法匹配GENERATE_SERIES产出的日期值 - 示例(补 2024-01-01 到 2024-01-31 每天):
SELECT gs.dt FROM generate_series('2024-01-01'::DATE, '2024-01-31'::DATE, '1 day'::INTERVAL) AS gs(dt)
LAG() 的唯一正经用途是定位缺口区间
- 直接用
LAG(event_time) OVER (ORDER BY event_time)算出上一行时间,再做差值判断是否跳天/跳小时,比全量生成再对比快得多 - 缺口识别语句模板:
SELECT event_time, LAG(event_time) OVER (ORDER BY event_time) AS prev_time, event_time - LAG(event_time) OVER (ORDER BY event_time) AS gap FROM events WHERE event_time >= '2024-01-01'; - 注意首行
prev_time是NULL,这是正常行为;别在WHERE里过滤它,否则漏掉第一条记录 - 如果发现
gap > INTERVAL '1 day',说明中间缺了至少一天——这时才该用GENERATE_SERIES(prev_time + INTERVAL '1 day', event_time - INTERVAL '1 day', '1 day')针对该段补,而不是硬扫全范围
补齐后聚合时,COALESCE(SUM(), 0) 不可省
-
LEFT JOIN后,缺失日期对应业务字段全是NULL,SUM(amount)自动忽略NULL,结果为NULL,不是0 -
COUNT(*)会把补出来的空行也计进去,而COUNT(amount)才只统计非空值;若要“每日订单数”,得用COALESCE(COUNT(amount), 0) - 错误写法:
SUM(t.amount)→ 图表里某天直接消失
正确写法:COALESCE(SUM(t.amount), 0) AS daily_sales -
GROUP BY必须用生成序列的字段(如gs.dt),不能用原始表的t.event_time(它可能是NULL)
真正的难点从来不在怎么写 GENERATE_SERIES,而在于:你是否清楚“该补哪一段”——盲目扫全量范围,在数据跨度大或用户多时,中间结果行数爆炸,查询从秒级变分钟级。先用 LAG/LEAD 定位真实缺口,再分段生成,才是线上可用的方案。










