STDDEV()和VARIANCE()默认计算样本标准差与方差(分母n−1),等价于STDDEV_SAMP()和VAR_SAMP();仅MySQL旧版等极少数情况例外,业务需总体统计时应显式使用STDDEV_POP()/VAR_POP()。

STDDEV() 和 VARIANCE() 到底算的是样本还是总体?
默认是样本(分母 n−1),不是总体。几乎所有主流数据库中:STDDEV() 等价于 STDDEV_SAMP(),VARIANCE() 等价于 VAR_SAMP()。只有 Oracle 和极老版 MySQL 可能例外——但别赌,默认行为不一致就硬写全称。
小数据组偏差明显:某组只有 3 条有效记录,STDDEV_SAMP() 比 STDDEV_POP() 高约 50%。业务上若分析的是全量订单(非抽样),却用了 STDDEV(),波动率就被高估了。
- 确认是否为总体数据 → 显式用
STDDEV_POP()和VAR_POP() - 不确定或属抽样 → 坚持用
STDDEV_SAMP(),别依赖STDDEV()别名 - MySQL 5.7 不支持
STDDEV_SAMP()?那就升级或手动算:SQRT(AVG(POWER(x - AVG(x), 2)))
GROUP BY 后结果全是 NULL,哪里卡住了?
不是函数坏了,是数据没过“最低门槛”:STDDEV_SAMP() 要求每组至少 2 个非 NULL 值,少于 2 就返回 NULL;STDDEV_POP() 单值组返回 0,但数学上无意义(业务上也不该当“无波动”)。
先查再算,避免盲目归因:
SELECT category, COUNT(*) AS total_cnt, COUNT(amount) AS valid_cnt, STDDEV_SAMP(amount) AS stddev FROM sales GROUP BY category;
-
valid_cnt = 1→ 必出NULL,得加HAVING COUNT(amount) > 1过滤 -
total_cnt ≠ valid_cnt→ 该组含大量NULL,需先决定清洗策略(剔除 or 填充中位数) - 想把单值组标为 0?用
CASE WHEN COUNT(amount) = 1 THEN 0 ELSE STDDEV_SAMP(amount) END
为什么变异系数(CV)比标准差更有业务意义?
标准差带单位、受量纲绑架:一组是“元”,一组是“万元”,STDDEV() 差一千倍,但实际波动可能一样。变异系数 STDDEV() / ABS(AVG()) 是无量纲比值,才能跨组公平比较相对离散程度。
但直接除很危险:
-
AVG()为 0 或接近 0(如净利率、复购率变化值)→ 除零崩溃或结果爆炸 - 稳妥写法:
NULLIF(AVG(amount), 0)让分母为 0 时直接产出NULL,再包一层CASE - 示例:
CASE WHEN ABS(AVG(amount))
窗口函数里套 STDDEV_SAMP() 为啥总出空?
窗口里的 STDDEV_SAMP() 仍按样本公式算,且严格要求当前窗口内至少有 2 个非 NULL 值。常见陷阱:
-
ROWS BETWEEN 29 PRECEDING AND CURRENT ROW→ 若日期不连续(比如跳过周末),实际行数可能 NULL - 正确做法:用时间范围而非行数,如 PostgreSQL/MySQL 8.0+ 支持
RANGE BETWEEN INTERVAL '29' DAY PRECEDING AND CURRENT ROW -
OVER (PARTITION BY category ORDER BY date)没写ROWS或RANGE→ 默认是GROUPS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,但部分引擎行为未定义 - 别混用:
GROUP BY+ 窗口STDDEV_SAMP() OVER ()会报错,必须全窗口或全分组
真正难的不是语法,是意识到:窗口标准差在首两行必然为空,而业务报表常要求“首日也显示数字”——这时就得接受用移动平均替代,或前端补逻辑,SQL 本身无法无中生有。

















