变异系数(cv)不能直接写为stddev_samp(x)/avg(x),因存在三个静默陷阱:单值组使标准差为null、均值为0导致除零、极小均值放大噪声;需先用having count(x)>1过滤,再用case判断均值绝对值是否大于1e-6。

变异系数(CV)= STDDEV_SAMP(x) / AVG(x),但直接除会崩——不是语法错,是业务逻辑错。它只在均值非零、且分组内至少有 2 个有效值时才有统计意义。
为什么变异系数不能直接写 STDDEV_SAMP(x)/AVG(x)
因为三个静默陷阱同时存在:
-
STDDEV_SAMP(x)在分组中COUNT(x) 时返回 <code>NULL,不是报错,你可能根本没注意到某几组“消失”了 -
AVG(x)对全 NULL 组返回NULL;对全 0 组(比如所有amount = 0)返回0.0,导致除零或INF - 浮点精度下
AVG(x)可能是1e-17这种极小值,看似非零,实则业务上等价于 0,一除就放大噪声
MySQL / PostgreSQL / Oracle 中安全计算 CV 的写法
必须拆解判断,不能靠 COALESCE 硬兜分母:
- 先用
HAVING COUNT(x) > 1排掉单值组(否则STDDEV_SAMP必为NULL) - 再用
CASE WHEN ABS(AVG(x)) 拦住接近零的均值 - 如果业务允许“零波动即 CV=0”,才考虑
CASE WHEN COUNT(x) > 1 AND ABS(AVG(x)) > 1e-6 THEN STDDEV_SAMP(x)/AVG(x) ELSE 0 END
示例(PostgreSQL):
SELECT
category,
COUNT(*) AS cnt,
COUNT(amount) AS non_null_cnt,
AVG(amount) AS avg_amt,
STDDEV_SAMP(amount) AS stddev_amt,
CASE
WHEN COUNT(amount) > 1 AND ABS(AVG(amount)) > 1e-6
THEN STDDEV_SAMP(amount) / AVG(amount)
ELSE NULL
END AS cv
FROM sales
GROUP BY category
HAVING COUNT(amount) > 1;
当 AVG(x) 接近 0 时,CV 失效,该换什么指标
CV 本质是相对离散度,分母塌缩后就失去可比性。这时应切换到绝对离散指标:
- 四分位距:
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY x) - PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY x)(PostgreSQL / Oracle 支持) - Hive 或旧版 MySQL 可用近似法:先
ROW_NUMBER()排序,再JOIN取上下四分位位置的值 - 如果只关心“是否稳定”,用
MAX(x) = MIN(x)布尔判断,比 CV 更鲁棒
真正难的不是写对那行除法,而是想清楚:你到底想回答“数值相对波动大不大”,还是“取值范围宽不宽”——前者依赖 CV,后者直接看 IQR 或极差。别让函数名带偏问题本质。










