STDDEV_SAMP()要求每组至少2个非NULL值,否则返回NULL;选STDDEV_SAMP还是STDDEV_POP取决于数据是样本还是总体,且须注意数据库默认行为差异及NULL处理。

直接用 STDDEV_SAMP() 和 VAR_SAMP(),但必须确认数据库是否支持、分组内是否有至少 2 个非空数值,否则结果为 NULL——这不是 bug,是数学定义决定的。
为什么 GROUP BY 后 STDDEV() 全是 NULL?
最常见原因是分组后有效数值不足 2 个:STDDEV_SAMP()(样本标准差)要求每组至少有 2 个非 NULL 值,否则无法计算(分母为 0)。STDDEV_POP() 虽能返回值,但单点无波动意义。
- 先检查:加
COUNT(*)和COUNT(amount)对比,确认每组非空行数 - 若
COUNT(amount) < 2,STDDEV_SAMP(amount)必为NULL -
amount列含字符串、空格或单位(如"1200元")会导致隐式转换失败,整行被跳过或报错 - 别依赖
COALESCE(STDDEV_SAMP(x), 0)硬兜——掩盖了数据稀疏问题
STDDEV_SAMP vs STDDEV_POP:该选哪个?
区别不在“准不准”,而在统计口径是否匹配业务场景:
- 用
STDDEV_SAMP():当分组数据是抽样(比如随机抽取的 100 笔订单代表整体质量) - 用
STDDEV_POP():当分组就是全量(比如某门店当日全部 37 笔交易) - MySQL 8.0+ 的
STDDEV()默认等价于STDDEV_SAMP();Oracle 和旧版 MySQL 可能指向STDDEV_POP(),不能默认 - PostgreSQL 和 SQL Server(用
STDEV())都明确区分,推荐显式写全名,避免跨库迁移翻车
窗口函数里套 STDDEV_SAMP 报错或结果异常?
典型问题不是语法错,而是帧定义和 NULL 处理没对齐:
-
STDDEV_SAMP(x) OVER (PARTITION BY category)是对每个 category 内所有行算标准差,没问题 - 但加上
ORDER BY date后,默认变成累积窗口(从第一行累加到当前行),首行必为NULL(单值无法算样本标准差) - 要滚动 30 天标准差,别用
ROWS BETWEEN 29 PRECEDING AND CURRENT ROW(日期可能不连续),改用RANGE BETWEEN INTERVAL '29' DAY PRECEDING AND CURRENT ROW(仅 PostgreSQL/MySQL 8.0+ 支持) - 窗口函数和
GROUP BY不能混用——要么全用窗口,要么全用分组聚合,否则报错
老版本 MySQL(如 5.7)或 SQLite 怎么办?
没有内置函数就别硬套,手动公式容易出精度和 NULL 问题:
- MySQL 5.7:用子查询先算均值,再关联计算平方差均值:
AVG(POWER(amount - avg_amount, 2)) - SQLite:原生无
STDDEV,启用 math 扩展后可用math_stddev();否则只能导出后用 Python 算 - 手算总体方差近似式
AVG(x*x) - AVG(x)*AVG(x)仅在无NULL且数据分布平稳时成立,有异常值时偏差大 - 生产环境遇到这类限制,优先升级数据库或把统计逻辑移到应用层,别在 SQL 里拼凑
真正难的不是写对函数,而是每次执行前心里清楚:这组数据到底算样本还是总体?有没有足够多的有效值?下游系统能不能正确处理 NULL?这些地方一松懈,波动率数字看着漂亮,实际已经失真。

















