变异系数cv=stddev()/avg(),需用case when或nullif防除零,分组计算须配group by,不同数据库stddev默认含义不同,推荐显式使用stddev_samp()并注意类型转换与空值处理。

SQL里没有现成的COEFFICIENT_OF_VARIATION函数,得自己算
变异系数(CV)= STDDEV() / AVG(),但直接除可能报错或结果异常——主要是AVG()为0或NULL时除零错误,以及不同数据库对STDDEV()默认偏态(样本 vs 总体)处理不一致。
实操建议:
-
STDDEV()在PostgreSQL/Oracle中默认是样本标准差(n−1),MySQL用STDDEV_SAMP()才明确;如需总体标准差(n),PostgreSQL用STDDEV_POP(),MySQL用STDDEV_POP(),SQL Server用STDEVP() - 必须用
CASE WHEN AVG(x) IS NULL OR AVG(x) = 0 THEN NULL ELSE STDDEV(x) / AVG(x) END兜底,不能裸写STDDEV(x)/AVG(x) - 分组后每组只出一个CV值,所以所有聚合必须套在
GROUP BY内,别漏掉分组字段
PostgreSQL分组计算CV的典型写法
以表sales按region分组算销售额变异系数为例:
SELECT
region,
CASE
WHEN AVG(amount) = 0 OR AVG(amount) IS NULL THEN NULL
ELSE STDDEV_SAMP(amount) / ABS(AVG(amount))
END AS cv
FROM sales
GROUP BY region;
注意点:
- 用
STDDEV_SAMP()而非STDDEV()更清晰,避免版本差异歧义 -
ABS(AVG(amount))防负均值导致CV为负(CV本应非负,若业务允许负均值,需确认是否真要保留符号) - 如果
amount含NULL,AVG()和STDDEV_SAMP()自动忽略,无需额外WHERE amount IS NOT NULL,但得清楚这是按非空行计算的
MySQL 8.0+兼容写法及NULL陷阱
MySQL不支持STDDEV_SAMP()在旧版(5.7及以前)中不可用,且AVG()遇全NULL组会返回NULL,此时STDDEV()也返回NULL,但直接相除会得NULL而非报错——看起来“成功”,实则掩盖了数据异常。
稳妥写法:
SELECT
category,
CASE
WHEN COUNT(*) = 0 THEN NULL
WHEN AVG(price) = 0 THEN NULL
ELSE STDDEV_SAMP(price) / AVG(price)
END AS cv
FROM products
GROUP BY category;
关键判断加了COUNT(*) = 0,因为即使AVG()为NULL,COUNT(*)仍可判空组;另外MySQL中STDDEV()等价于STDDEV_SAMP(),但显式写更易读。
SQL Server里用STDEV()和AVG()要注意浮点精度
SQL Server的AVG()默认返回与输入类型相同的数值类型(如INT列→结果截断为INT),导致AVG(1,2,3) = 2,再除STDEV()(返回FLOAT)时隐式转成FLOAT但精度已损。
必须显式转类型:
SELECT
dept,
CASE
WHEN AVG(CAST(salary AS FLOAT)) = 0 THEN NULL
ELSE STDEV(salary) / AVG(CAST(salary AS FLOAT))
END AS cv
FROM employees
GROUP BY dept;
否则可能得到CV=0.333…算成0.33甚至0——尤其当均值是整数、标准差很小时,误差会被放大。
真正麻烦的是跨数据库迁移:同一段CV逻辑,在PostgreSQL里跑得通,到SQL Server里因类型隐式转换出偏差,又没报错,很容易被当成“数据正常”。这种静默偏差,比报错更难排查。










