窗口函数比GROUP BY更适配物联网时序数据,因其保留原始时间精度、支持按设备分区、可控滑动窗口及异常检测;关键需正确设置PARTITION BY、ORDER BY和ROWS范围。

直接用 AVG() 配合 OVER() 就能实现基础平滑,但关键在于窗口帧的定义是否匹配设备采样节奏——错配会导致延迟响应或滤波失效。
为什么不能只用 GROUP BY 做平滑?
物联网数据是时序流,每条记录带 Timestamp,且设备持续上报。用 GROUP BY FLOOR(DATEDIFF(second, '1970-01-01', Timestamp) / 5) 这类时间分桶会丢失原始时间精度,破坏事件顺序;更严重的是,它把不同设备的数据混在同一组里计算平均值,造成跨设备污染。
窗口函数保留每行原始记录,只在逻辑窗口内聚合,天然适配多设备、高频率、带时间戳的场景。
-
PARTITION BY DeviceID是必须的,否则温度传感器和湿度传感器的值会被一起平均 - 没加
ORDER BY Timestamp的窗口默认无序,ROWS BETWEEN 2 PRECEDING AND CURRENT ROW可能取到未来时间点的数据(尤其当写入乱序时) - T-SQL 中
GETDATE()写入时间 ≠ 设备实际采集时间,应优先用设备自带的ReportTime字段排序
用 LAG/LEAD 检测突变值再过滤
单纯移动平均对阶跃型噪声(比如传感器瞬时跳变)抑制有限。先识别异常点,再决定是否剔除或替换,比全量平滑更可控。
典型做法是:对每个设备,用 LAG() 取上一行值,计算与当前值的绝对差,超过阈值即标为噪声:
SELECT
DeviceID,
ReportTime,
Temperature,
ABS(Temperature - LAG(Temperature) OVER (
PARTITION BY DeviceID ORDER BY ReportTime
)) AS delta_from_prev,
CASE
WHEN ABS(Temperature - LAG(Temperature) OVER (
PARTITION BY DeviceID ORDER BY ReportTime
)) > 5 THEN NULL
ELSE Temperature
END AS clean_temp
FROM SensorData;- 这个
CASE不修改原表,只生成清洗后视图,适合调试阶段验证阈值合理性 -
LAG()默认取前 1 行,不加OFFSET参数就别指望跳过坏点再取“上上行” - 若设备偶发断连,
LAG()会跳过空值直接取更早的有效行,这点在 T-SQL 和 PostgreSQL 中行为一致,但在 MySQL 8.0+ 需确认是否启用IGNORE NULLS
滑动窗口大小怎么选?看采样间隔和噪声周期
窗口不是越大越好。假设温湿度传感器每 10 秒上报一次,而环境真实变化周期在分钟级,用 ROWS BETWEEN 11 PRECEDING AND CURRENT ROW(覆盖近 2 分钟)就足够;若设成 60 行(10 分钟),平滑过度,反而掩盖真实升温趋势。
- 实操建议:先用
COUNT(*) OVER (PARTITION BY DeviceID ORDER BY ReportTime ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)看每台设备历史数据密度,避免窗口长度超过实际连续数据段 -
ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING是对称窗口,适合离线回填;实时流处理推荐ROWS BETWEEN 4 PRECEDING AND CURRENT ROW(仅依赖历史,不等未来) - T-SQL 不支持
RANGE BETWEEN的时间偏移(如RANGE BETWEEN INTERVAL '30 SECONDS' PRECEDING AND CURRENT ROW),必须转成等效行数,靠ReportTime排序后硬算
真正难的不是写对语法,而是确认 PARTITION BY 的维度是否覆盖了所有干扰源——比如同一批次部署的设备可能存在共模噪声,这时光按 DeviceID 分区不够,还得加入 DeploymentBatch 或 LocationZone。漏掉这一层,平滑结果看起来很稳,实则把系统性偏差当成了正常波动。

















