标准差法是检测近似正态分布数值型离群点最直接的手段,需用子查询或窗口函数先计算AVG()和STDDEV(),再结合z_score>3等条件过滤,同时注意NULL处理、类型转换、采样偏差及时区对齐等问题。

用 AVG() 和 STDDEV() 快速圈定数值型离群点
标准差法是最直接的离群检测手段,适用于近似正态分布的字段(如订单金额、用户停留时长)。关键不是单纯算出平均值,而是结合标准差划定合理波动范围。
常见错误是直接写 WHERE amount > AVG(amount) + 2 * STDDEV(amount) —— 这会报错,因为聚合函数不能直接出现在 WHERE 子句中。
- 必须用子查询或窗口函数:先算出全局
AVG()和STDDEV(),再与原表做JOIN或用OVER() - PostgreSQL/MySQL 8.0+ 支持:
SELECT *, ABS(amount - AVG(amount) OVER()) / NULLIF(STDDEV(amount) OVER(), 0) AS z_score FROM orders,然后过滤z_score > 3 -
NULLIF(STDDEV(...), 0)必须加,否则标准差为 0 时会触发除零错误 - 对小样本(STDDEV() 估计不稳定,容易误标
用 PERCENTILE_CONT() 做稳健的四分位距(IQR)检测
IQR 法不依赖分布假设,比标准差更抗噪,尤其适合含大量零值或长尾数据(如日活用户数、API 响应时间)。
难点在于不同数据库语法差异大,且 PERCENTILE_CONT() 是窗口函数,不能直接用于 GROUP BY 后的聚合结果。
- PostgreSQL 写法:
PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY response_time)(聚合版);SQL Server 用PERCENTILE_CONT(0.25) OVER (ORDER BY response_time)(窗口版) - 计算 IQR 后,上下界分别是
Q1 - 1.5 * IQR和Q3 + 1.5 * IQR,注意边界外的数据才是离群,边界上不算 - 如果字段含大量
NULL,PERCENTILE_CONT()默认忽略,但需确认业务是否允许丢弃这些行 - 在 WHERE 条件里嵌套百分位计算会导致全表扫描,建议先存成临时表或 CTE
用 COUNT() OVER(PARTITION BY ...) 发现分类维度下的异常频次
离群不只发生在数值大小,也常出现在频次分布中——比如某个 user_id 一天内下单 200 次,而其他人平均只有 2 次。
这时不能只看单字段极值,得按业务维度分组统计后,再识别组内异常。
- 典型写法:
COUNT(*) OVER (PARTITION BY user_id ORDER BY order_time ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)可算累计频次,但更常用的是无序窗口:COUNT(*) OVER (PARTITION BY user_id) - 别漏掉
DISTINCT场景:比如检测「同一设备 ID 关联了 10 个不同手机号」,要用COUNT(DISTINCT phone) OVER (PARTITION BY device_id) - 窗口函数结果不能直接用于
HAVING,必须外裹一层查询才能加WHERE cnt > 10 - 若分区键基数过高(如亿级
user_id),OVER(PARTITION BY ...)可能内存溢出,此时改用GROUP BY + HAVING更稳妥
警惕 NULL、类型隐式转换和采样偏差带来的假阳性
真正上线跑离群检测时,一半问题不出在算法,而出在数据质量本身。
-
AVG()、STDDEV()、PERCENTILE_CONT()全部自动忽略NULL,但如果业务上NULL代表“异常未上报”,那直接忽略就漏掉了最大一批离群 - 字符串字段误当数值处理:比如
amount实际是VARCHAR类型,含 “$100” 或 “N/A”,CAST(amount AS NUMERIC)会报错或转成 0,污染统计基线 - 用
LIMIT随机采样检测?危险。离群值稀疏时,小样本大概率抽不到,得出“数据很干净”的错误结论 - 时间范围没对齐:查“昨日异常”,却用
WHERE event_time > CURRENT_DATE - INTERVAL '1 day',没考虑时区或分区表实际落盘延迟
离群检测脚本上线前,务必拿已知异常样本反向验证召回率——这点最容易被跳过。

















