用LAG()计算时间差可识别相邻记录断档,GENERATE_SERIES()补全时间轴可发现大段空白,ROW_NUMBER()或序号差可校验逻辑连续性;须注意排序、时区、NULL及索引。

用 LAG() 和时间差判断相邻记录是否断档
时间序列缺失的本质是:按时间排序后,当前行与上一行的间隔超出预期(比如每5分钟一条,却隔了15分钟)。关键不是查“空值”,而是查“时间跳跃”。LAG() 是最直接的工具,它能拿到前一行的 timestamp 值,配合减法就能算出间隔。
实操建议:
- 确保查询中用
ORDER BY timestamp显式排序,窗口函数依赖此顺序,否则LAG()结果不可靠 - 时间字段必须是
TIMESTAMP WITH TIME ZONE或TIMESTAMP WITHOUT TIME ZONE,不能是TEXT或DATE;否则减法会报错或返回意外结果 - 间隔阈值用
INTERVAL '5 minutes'这类字面量最安全,避免用整数秒再换算(易出时区/精度偏差) - 示例片段:
SELECT ts, LAG(ts) OVER (ORDER BY ts) AS prev_ts, ts - LAG(ts) OVER (ORDER BY ts) AS gap FROM sensor_data WHERE ts - LAG(ts) OVER (ORDER BY ts) > INTERVAL '6 minutes';
用 GENERATE_SERIES() 补全时间轴再做左连接识别缺失
当你要检测“某段时间内完全没数据”的区间(比如凌晨2–3点整点都没上报),LAG() 就不够用了——它只能发现相邻断点,无法发现开头、结尾或大段空白。这时得主动构造完整时间轴,再和原始数据比对。
实操建议:
-
GENERATE_SERIES(start, stop, step)的step必须与业务粒度一致,例如每10分钟一条就写INTERVAL '10 minutes';写成'600 seconds'可能因时区转换导致边界偏移 - 左连接时,用
ON series.ts = data.timestamp,别用data.timestamp BETWEEN ...,后者会导致笛卡尔积膨胀 - 原始表若无索引,对
timestamp字段加 B-tree 索引能显著提升连接性能,尤其在百万级以上数据时 - 示例片段:
SELECT s.ts AS expected_time FROM GENERATE_SERIES('2024-01-01 00:00', '2024-01-01 23:59', '1 hour'::INTERVAL) AS s(ts) LEFT JOIN sensor_data d ON s.ts = d.timestamp WHERE d.timestamp IS NULL;
用 ROW_NUMBER() 配合等差序列检测“本该存在但没出现”的序号断层
某些场景下,时间戳本身可能不准(如设备本地时钟漂移),但业务逻辑保证每条记录应有严格递增的逻辑序号(如 seq_id)。此时用时间差判断反而失真,改用序号连续性更可靠。
实操建议:
-
ROW_NUMBER() OVER (ORDER BY timestamp)生成的是物理顺序号,不是原始seq_id;若要校验业务序号,直接用seq_id - LAG(seq_id) OVER (ORDER BY timestamp) - 注意
seq_id类型:如果是BIGINT,减法没问题;若是TEXT(如 "0001", "0002"),必须先::BIGINT转换,否则字符串减法会报错 - 若序号允许跳变(如批量插入导致 +100),需把固定阈值换成动态基线,例如
seq_id - LAG(seq_id) > (SELECT AVG(seq_id - LAG(seq_id)) OVER (...)) * 2
性能与边界情况必须检查的三处
窗口函数本身不慢,但错误用法会让查询从秒级变成分钟级,甚至 OOM。最容易被忽略的是这三点:
- 分区表未加
PARTITION BY:如果数据按天分表,但窗口函数没写PARTITION BY DATE(timestamp),就会跨天计算LAG(),把昨天最后一行和今天第一行当成相邻,误报“断档” - NULL 时间戳未过滤:
LAG()遇到NULL会返回NULL,ts - NULL仍是NULL,而NULL > INTERVAL '5 min'永远为FALSE,导致缺失值漏检;务必在WHERE或FILTER中排除timestamp IS NULL - 时区混用:数据库
timezone设为'UTC',但应用写入的是'Asia/Shanghai'字符串且未显式转换,会导致ts值整体偏移 8 小时,所有间隔计算全错;统一用AT TIME ZONE校准,例如timestamp AT TIME ZONE 'UTC'
实际跑起来之前,先用 EXPLAIN ANALYZE 看一眼执行计划里有没有 WindowAgg 节点出现在大表全扫描之后——如果有,说明排序没走索引,得调。

















