应优先使用 VAR_SAMP() 或 VAR_POP() 配合 GROUP BY 计算方差,二者分别适用于样本推断和总体分析场景,现代数据库普遍支持,需注意数据类型、NULL 处理及跨库函数语义差异。

直接用 VAR_SAMP() 或 VAR_POP() 配合 GROUP BY 就行,别手写平方差——除非你用的是 MySQL 5.7 或更老版本。
GROUP BY + VAR_SAMP() 是最简方案
绝大多数现代数据库(PostgreSQL、MySQL 8.0+、SQL Server 2012+、Oracle)都支持标准方差聚合函数。关键不是“能不能算”,而是“该用哪个”:
-
VAR_SAMP(col):样本方差,分母为COUNT(*) - 1,适用于从分组中抽样推断更大总体的场景(比如某日订单代表全站波动趋势) -
VAR_POP(col):总体方差,分母为COUNT(*),适用于该分组本身就是全部数据(比如“2024年华东区所有门店销售额”) - 两者都自动忽略
NULL值;整组全NULL或仅一个非空值时,VAR_SAMP()返回NULL,VAR_POP()返回0.0 - 错误现象:
ERROR: function var(text) does not exist→ 检查列类型,VAR_*只接受数值型,必要时加::numeric强转
MySQL 5.7 必须手写,且容易漏掉 NULL 处理
它不支持 VAR_SAMP(),但支持 VARIANCE()(等价于 VAR_SAMP())和 VAR_POP()。如果你被限制在 5.7 且想兼容旧逻辑,手动实现要绕开嵌套聚合:
- 用子查询先算每组均值,再 JOIN 回原表算偏差平方:
AVG((amount - avg_amount) * (amount - avg_amount)) - 务必在子查询里写
GROUP BY region,且主查询用USING (region)时,确保两边都有该字段 -
amount含NULL会导致整行(amount - avg_amount)为NULL,最终结果变NULL—— 要过滤或补默认值,比如WHERE amount IS NOT NULL - 想算样本方差?把
AVG()换成SUM() / NULLIF(COUNT(*) - 1, 0),否则单组一行会除零
窗口函数里算分组内方差,别加 ORDER BY
用 VAR_SAMP(col) OVER (PARTITION BY group_col) 最干净,但要注意:
-
PARTITION BY定义分组维度,ORDER BY在方差计算中通常不需要——加了会触发范围帧(range frame),导致结果变成“到当前行为止”的累积方差,而非整组方差 -
VAR_SAMP()窗口函数要求分区至少有 2 行非空值,否则返回NULL;若需单行返回 0,得套CASE WHEN COUNT(col) OVER (PARTITION BY x) = 1 THEN 0 ELSE VAR_SAMP(col) OVER (...) END - 调试时先查
COUNT(col) OVER (PARTITION BY x)和COUNT(*) OVER (PARTITION BY x),确认是否因NULL导致有效值数量不符
跨数据库移植时,STDDEV 名称和语义不统一
别凭经验写 STDDEV(col),不同库默认行为相反:
- MySQL 和 Oracle:`STDDEV()` 默认是总体版(等价
STDDEV_POP()) - PostgreSQL 和 SQL Server:`STDDEV()` 默认是样本版(等价
STDDEV_SAMP()) - SQLite:不支持任何方差函数,必须手写
AVG(x*x) - AVG(x)*AVG(x)(仅当无NULL时数学等价) - 安全做法:一律显式写全称函数名,如
STDDEV_POP()或VAR_SAMP(),并在迁移前验证单组两行数据的结果是否一致
真正麻烦的不是语法,而是搞不清业务到底要“全体离散程度”还是“样本估计值”——选错 POP 还是 SAMP,结果会系统性偏高或偏低,且很难通过肉眼发现。

















