绝大多数业务场景应使用stddev_samp(),因其基于样本且分母减1;stddev_pop()仅适用于全量总体数据,跨库需注意mysql 5.7前stddev默认为pop、postgresql/sql server默认为samp。

STDDEV_SAMP 和 STDDEV_POP 到底该用哪个
绝大多数业务场景下,你手头的数据只是总体的一个样本(比如某月订单、某部门员工、某批次日志),不是全量数据。这时必须用 STDDEV_SAMP() —— 它分母是 COUNT(*) - 1,做了贝塞尔校正;而 STDDEV_POP() 分母是 COUNT(*),只适用于你 100% 确认数据就是全体、无抽样偏差的情况。
容易踩的坑:
- 在 PostgreSQL 或 SQL Server 里写
STDDEV(x),默认就是STDDEV_SAMP(x);但在 MySQL 5.7 及以前,STDDEV(x)实际等价于STDDEV_POP(x),语义相反 - 组内只有 1 条记录时,
STDDEV_SAMP()返回NULL(数学上无法计算),而STDDEV_POP()返回0—— 看似“没波动”,实则掩盖了数据稀疏问题 - 跨库迁移 SQL 时,别直接复制函数名,先查目标库文档确认默认行为
GROUP BY 后直接聚合 vs 窗口函数 OVER(PARTITION BY)
想按部门看薪资标准差,且结果只要一行/部门:用 GROUP BY + 聚合函数;想保留原始行数、每行都带本部门的标准差(比如后续还要算“该员工薪资离部门均值几个标准差”):必须用窗口函数。
常见错误现象:
-
SELECT dept, salary, STDDEV_SAMP(salary) OVER (PARTITION BY dept) FROM emp;在 MySQL 8.0+、PostgreSQL 9.4+、Oracle、BigQuery 中可用;在 MySQL 5.7 或 SQLite 中会报错或不识别 - 写了
OVER (PARTITION BY dept ORDER BY hire_date)却没加ROWS BETWEEN,数据库默认按“从第一行到当前行”累积计算,结果每行标准差都不同,失去分组意义 - 要静态分组标准差,只写
PARTITION BY dept,删掉ORDER BY
MySQL 5.7 或不支持窗口函数的库怎么硬刚
没有 OVER 就只能用子查询或 JOIN 模拟:先算各组均值,再回连原表算偏差平方,最后聚合求平均再开方。
示例(按 region 算样本标准差):
SELECT region,
SQRT(SUM((amount - avg_amount) * (amount - avg_amount)) / (COUNT(*) - 1)) AS stddev_samp
FROM sales
JOIN (SELECT region, AVG(amount) AS avg_amount FROM sales GROUP BY region) t USING (region)
GROUP BY region
HAVING COUNT(*) > 1;
注意点:
-
USING (region)要求主表和子查询都有region字段,漏写子查询里的GROUP BY region会出错 - 必须加
HAVING COUNT(*) > 1,否则除零崩溃 - 如果
amount含NULL,AVG()自动跳过,但(amount - avg_amount)遇NULL整行变NULL,最终结果为NULL;需提前WHERE amount IS NOT NULL
标准差结果为 NULL 或 0 的真实原因
STDDEV_SAMP() 返回 NULL,大概率是组内有效数值少于 2 个;STDDEV_POP() 返回 0,说明组内所有非空值完全相等(或只剩一个值)。但这两种情况业务含义完全不同。
建议做法:
- 加
HAVING COUNT(*) >= 5过滤掉小样本组,避免统计失真 - 同时输出
COUNT(*)和STDDEV_SAMP(),人工判断是否因数据量不足导致不可信 - 不要单独依赖标准差数值做告警,配合均值、极差(
MAX() - MIN())一起看
最常被忽略的一点:标准差本身对异常值极度敏感。一组数据里混入一个离群高薪,就能把 STDDEV_SAMP() 拉高好几倍——它反映的是“整体离散程度”,不是“典型波动范围”。真要稳健评估,得考虑中位数绝对偏差(MAD)或分位数间距(IQR),但那已是另一套 SQL 写法了。











