avg() over仅计算窗口内均值,不提供偏离度量,无法单独用于异常检测;必须结合偏差计算、标准差、分位数或lag等手段构建判断逻辑,并注意分区合理性、除零防护和样本量控制。

AVG() 本身不识别异常,AVG() OVER 也只是算局部均值——真正能揪出离群值的,是把它和原始值做差、再结合阈值或标准差逻辑来判断。直接用 AVG() OVER 输出一个“平滑值”不等于找到了异常,那只是第一步。
为什么 AVG OVER 单独用不了异常检测?
AVG() OVER 只负责在一个窗口内求平均,它不提供任何偏离度量。你看到某行 value = 150,avg_7d = 80,这看起来像异常,但没上下文就无法判定:是数据真跳变?还是窗口太小被噪声带偏?还是这组数据本来方差就大?
所以必须搭配以下至少一种手段:
- 显式计算偏差:
ABS(value - AVG(value) OVER (...)) - 结合标准差:
STDDEV_SAMP(value) OVER (...)构造 Z-score - 嵌套分位数:
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY ABS(value - avg_val))
用 AVG + STDDEV 做 Z-score 式判断(推荐检验科/实验室场景)
适合指标分布近似正态、且需快速标记偏离群体均值的样本,比如肌酐值、血糖读数。
关键点不是“算平均”,而是“在合理分组内算+防除零+控样本量”:
- 必须加
PARTITION BY test_item, lab_batch_id:不同试剂批次、不同检测项目不能混算均值,否则校准差异会淹没真实异常 - 用
NULLIF(AVG(value), 0)防止分母为 0;用HAVING COUNT(*) > 4过滤小样本组(STDDEV_SAMP()在 n=1 时返回 NULL,n=2 时极不稳定) - Z-score 判断写成:
ABS(value - avg_val) > 2 * stddev_val,而不是(value - avg_val) / NULLIF(stddev_val, 0) > 2——避免 stddev_val=0 时整列崩掉
示例片段:
SELECT test_item, sample_id, value,
AVG(value) OVER (PARTITION BY test_item, lab_batch_id) AS avg_val,
STDDEV_SAMP(value) OVER (PARTITION BY test_item, lab_batch_id) AS stddev_val
FROM lab_results
WHERE value IS NOT NULL;
用 AVG OVER 搭配 LAG 做突变检测(适合排班/申贷/传感器场景)
当异常表现为“相邻点剧烈变化”,比如护士连续两班总工时超 16 小时、设备秒级申请间隔 AVG() OVER 要配合 LAG() 才有效。
常见错误和对策:
- 只算
AVG(work_hours) OVER (PARTITION BY nurse_id ORDER BY shift_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW),但没和当前值比——得补上WHERE work_hours > 1.5 * avg_7d这类条件 -
LAG()返回 NULL 时,work_hours - LAG()结果为 NULL,WHERE 条件直接跳过首班次——应改用work_hours - COALESCE(LAG(work_hours), 0) > 8 - 时间字段含毫秒重复?
ORDER BY shift_date, shift_start_time必须补全,否则LAG()取值顺序不可控
边界与性能最容易被忽略的三个点
很多人调通了 SQL 就以为搞定,但上线后常在这些地方翻车:
-
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW算的是“最近 3 条记录”,不是“最近 3 天”——若某天缺数据,窗口不会自动往前多抓一天,结果天数会缩水 - MySQL 8.0 不支持
RANGE BETWEEN INTERVAL '3 DAYS' PRECEDING,想按日历天滑动就得先生成日期序列再 LEFT JOIN 补全,否则基线漂移 - 在大表上对
timestamp开窗,没建索引会导致全表排序,执行计划里出现WindowAgg + Sort节点且耗时陡增——CREATE INDEX ON table (device_id, apply_time)能直接降一个数量级










