时间序列统计需先对齐时间轴再聚合,用GENERATE_SERIES或递归CTE构造标准锚点;再通过窗口函数(如AVG OVER、LAG)实现滑动分析;最后合理处理空值,用count对比识别缺失率,优先前向填充而非硬填0。

sql时间序列统计不是简单加个GROUP BY time就能搞定的事——核心在于理解“时间对齐”“窗口切分”和“空值填充”这三个关键动作。搞清它们,写出来的查询才真正反映业务趋势,而不是数据幻觉。
时间序列 ≠ 普通分组:先对齐,再聚合
原始时间戳往往不规整(比如传感器每15秒一条、但有时漏采、有时重复),直接按分钟/小时GROUP BY DATE_TRUNC('hour', ts)会丢失节奏感。正确做法是主动构造标准时间轴,再左连接原始数据。
- 用
GENERATE_SERIES(postgresql)或递归CTE(mysql 8.0+/SQL Server)生成连续时间点,比如每5分钟一个锚点 - 把原始数据按就近原则归入这些锚点(常用
ts >= anchor AND ts ) - 再对每个锚点做
AVG(value)、COUNT(*)等聚合——这时的“每5分钟均值”才有可比性
滑动窗口分析:用OVER()抓住动态变化
同比、环比、移动平均这些指标,本质是让当前行“看到”前后若干行的时间数据。靠子查询嵌套既慢又难读,窗口函数才是正解。
-
AVG(value) OVER (ORDER BY ts ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)→ 7点移动平均(含当前) -
LAG(value, 1) OVER (ORDER BY ts)→ 取前一行值,减一下就是环比变化量 -
value - FIRST_VALUE(value) OVER (ORDER BY ts ROWS UNBOUNDED PRECEDING)→ 累计增幅
注意:必须显式ORDER BY,否则窗口无意义;ROWS比RANGE更可控,避免时间相同时的意外聚合。
空值不是消失,是需要解释的信号
时间序列里大量NULL很常见(设备离线、上报失败)。直接AVG()会跳过它们,但“零值”和“无数据”语义完全不同。
- 用
COUNT(value)和COUNT(*)对比,能发现缺失率(比如某小时有60条记录但只有45个value,说明25%数据丢失) - 用
COALESCE(value, 0)要谨慎——填0可能扭曲均值;更适合场景是“是否活跃”这类布尔统计 - 真需要补值?优先用
LAST_VALUE(value IGNORE NULLS) OVER (...)做前向填充,比固定填0/中位数更贴近实际
基本上就这些。时间序列统计的难点不在语法,而在明确你想回答的问题:是看静态分布?还是追踪变化节奏?或是诊断数据质量?想清楚这点,SQL自然就顺了。