STDDEV()和VARIANCE()可直接用于分组计算,但默认行为因数据库而异:PostgreSQL/Oracle默认样本(n−1),MySQL默认总体(n),SQL Server不支持STDDEV()而需用STDEV()/STDEVP();建议统一使用STDDEV_SAMP()、STDDEV_POP()等全称函数,并注意NULL处理、单值组返回NULL或0、变异系数更适于跨量纲比较。

STDDEV() 和 VARIANCE() 能直接用,但别信默认行为
绝大多数主流数据库(PostgreSQL、Oracle、MySQL 8.0+、SQL Server)确实内置了 STDDEV() 和 VARIANCE(),但它们默认算的到底是「样本」还是「总体」,差别很大:STDDEV() 在 PostgreSQL 和 Oracle 中等价于 STDDEV_SAMP()(除以 n−1),而 MySQL 的 STDDEV() 实际是 STDDEV_POP() 别名(除以 n)。SQL Server 根本不认 STDDEV(),只认 STDEV()(样本)和 STDEVP()(总体)。不显式写全称,跨库迁移或换人维护时极易出错。
建议一律使用带后缀的全称函数:STDDEV_SAMP()、STDDEV_POP()、VARIANCE_SAMP()、VARIANCE_POP()。MySQL 5.7 不支持这些?那就别硬套——直接跳到手动计算部分。
GROUP BY 后结果为 NULL?先查 COUNT(col) 和 COUNT(*)
STDDEV_SAMP() 要求每组至少有 2 个非 NULL 数值,否则返回 NULL;STDDEV_POP() 单值组会返回 0,但这个 0 没统计意义。很多人看到 NULL 就以为函数挂了,其实是数据本身不满足计算前提。
- 用
COUNT(salary)查实际参与计算的非 NULL 行数,和COUNT(*)对比,确认是否有大量隐性空值 - 若某组
COUNT(salary) = 1,STDDEV_SAMP()必为NULL,这不是 bug,是统计定义使然 - 需要把 NULL 当 0 处理?必须提前用
COALESCE(salary, 0),不能依赖函数内部自动填充 - 整组全为 NULL?所有变体都返回
NULL,不是 0,下游应用得能处理
窗口函数里 STDDEV_SAMP() 返回一堆 NULL?检查分组行数和 ORDER BY
写 STDDEV_SAMP(salary) OVER (PARTITION BY dept) 却发现很多 NULL?大概率是某些部门只有 1 名员工,或者 salary 字段在该部门内只有一行非 NULL 值。更隐蔽的问题是误加了 ORDER BY:
- 加
ORDER BY hire_date后,窗口变成“从第一行累加到当前行”,首行永远NULL(单值无法算样本标准差) - 要的是“整个部门的离散程度”,就只用
PARTITION BY dept,不要ORDER BY - 真需要滚动窗口(如最近 7 天)?显式写
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW,别靠ORDER BY隐式推导 - SQLite 完全不支持
STDDEV(),强行用会报no such function: STDDEV,必须换扩展或应用层计算
变异系数(CV)比单独看标准差更有业务意义
直接比较不同分组的 STDDEV_SAMP() 常常失真——比如一组是日均销售额(万元级),另一组是用户停留时长(秒级),数值量纲和量级完全不同。这时该算变异系数:STDDEV_SAMP(x) / NULLIF(AVG(x), 0)。
- 用
NULLIF(AVG(x), 0)防止除零,比CASE WHEN AVG(x) = 0 THEN NULL ELSE ... END更简洁 - 均值接近 0(如净利率、转化率)时,CV 会剧烈放大噪声,此时应改用绝对平均偏差或四分位距(IQR)
- 方差单位是原始单位的平方(如「元²」),标准差才是同单位;拿
VARIANCE()和均值直接比,数值天然偏大,容易误判 - MySQL 中浮点精度可能导致
AVG(x)极小却不为 0,建议加ABS():STDDEV_SAMP(x) / NULLIF(ABS(AVG(x)), 0)
最常被忽略的点:单值分组的 STDDEV_POP() 返回 0,看起来“很稳定”,实则毫无信息量;而 STDDEV_SAMP() 返回 NULL 反而是更诚实的信号。别为了填满报表强行用 COALESCE 塞 0——先确认这组到底有没有波动分析价值。

















