在数据分析场景里,均值和标准差只能描述数据的集中趋势与离散程度,却无法回答一个同样重要的问题:数据分布到底长什么样?是向左歪还是向右歪,是尖峰还是平顶?这就轮到偏度和峰度登场了。本文围绕SQL环境下如何计算这两个统计量展开,既介绍现成的SKEWNESS与KURTOSIS聚合函数,也给出纯手写SQL的实现思路。

偏度与峰度的统计学含义
偏度衡量的是分布的不对称性。当一个分布的右尾更长、右侧存在少量极大值时,大量数据集中在左侧,此时偏度为正,称为右偏或正偏,典型的例子是人均收入分布。反之,若左尾更长,偏度为负,称为左偏。正态分布的偏度为0,也就是说数据左右基本对称。
峰度衡量的是分布峰形的陡峭程度。正态分布的峰度约为3,很多工具会减去3后输出,即所谓超额峰度,让正态分布的基准值变为0。峰度大于0说明分布比正态分布更尖、尾部更厚,极端值更多;峰度小于0则说明分布更平坦,数据分布较为均匀。在风控、质检等场景中,厚尾往往意味着小概率的大额损失风险,峰度是识别这类风险的重要信号。
这两个指标配合使用,可以快速判断一组业务数据是否近似服从正态分布,从而决定后续该用参数检验还是非参数方法,或者判断均值是否会被极端值严重扭曲。
主流数据库中的SKEWNESS与KURTOSIS函数
不同数据库对这两个函数的支持差异较大。DuckDB从0.10版本开始提供原生的skewness()和kurtosis()聚合函数,ClickHouse则提供skewness和kurtosis聚合函数,PostgreSQL需要安装madlib扩展或自写聚合,MySQL和SQL Server目前没有内置支持。
以DuckDB为例,直接对目标列做聚合即可:
-- 计算销售额的偏度与峰度
SELECT
skewness(amount) AS skew_val,
kurtosis(amount) AS kurt_val, -- 超额峰度,正态分布约为0
avg(amount) AS mean_val,
stddev_samp(amount) AS std_val
FROM sales;
-- 按渠道分组计算
SELECT
channel,
count(*) AS cnt,
skewness(amount) AS skew_val,
kurtosis(amount) AS kurt_val
FROM sales
GROUP BY channel
ORDER BY skew_val DESC;
需要注意的是,各数据库采用的公式并不统一。有的实现计算样本偏度(分母乘以校正系数n/((n-1)(n-2))),有的直接计算总体矩,峰度也分是否减3两种口径。在使用之前务必查阅对应文档,确认口径后再与其他工具(如Python的scipy)的结果对齐,避免出现两边数值对不上的困惑。
ClickHouse中的用法类似,直接对目标列聚合即可得到结果,适合在OLAP场景下对海量数据做分布形态分析。
没有内置函数时的手写SQL实现
如果使用的是MySQL或SQL Server这类没有原生支持的数据库,可以借助聚合表达式手动实现。以样本偏度和样本峰度的经典公式为例,SQL实现的思路是先算出均值和标准差,再用SUM累加三阶、四阶中心矩。
直接在一次聚合里完成会遇到问题:聚合函数不能嵌套引用另一个聚合的结果,比如AVG(x - AVG(x))这种写法会直接报错。解决办法是拆成两层子查询,先算均值,再基于均值计算各阶中心矩:
-- MySQL 8.0 实现:总体偏度与超额峰度
SELECT
n,
m3 / POW(m2, 1.5) AS skew_val, -- 偏度 = 三阶中心矩 / 方差的1.5次方
(m4 / POW(m2, 2)) - 3 AS kurt_val -- 超额峰度 = 四阶中心矩/方差平方 - 3
FROM (
SELECT
COUNT(*) AS n,
AVG(POW(d.amount - s.mu, 2)) AS m2,
AVG(POW(d.amount - s.mu, 3)) AS m3,
AVG(POW(d.amount - s.mu, 4)) AS m4
FROM sales d
CROSS JOIN (
SELECT AVG(amount) AS mu FROM sales
) s
) t;
另一种更优雅的思路是利用窗口函数,先给每行打上全局均值标签,再在外层做聚合,这样只需扫描一遍数据:
SELECT
AVG(POW(amount - mu, 3)) / POW(AVG(POW(amount - mu, 2)), 1.5) AS skew_val,
AVG(POW(amount - mu, 4)) / POW(AVG(POW(amount - mu, 2)), 2) - 3 AS kurt_val
FROM (
SELECT
amount,
AVG(amount) OVER () AS mu
FROM sales
) t;
这种写法逻辑清晰,而且非常容易改成分组版本,只需把OVER ()改为OVER (PARTITION BY channel),外层配合GROUP BY channel,就能在每个业务维度上分别计算偏度和峰度,实现分布形态的横向对比。
如果嫌手写公式太长,还可以在支持自定义聚合的数据库里把公式封装成用户自定义聚合函数(UDA),一次封装,处处复用,团队其他人调用时就像使用内置函数一样简单。
实际应用中的注意事项
第一,样本量要足够大。偏度和峰度对极端值极其敏感,当数据行数少于20时,计算结果波动很大,参考意义有限。建议在SQL中同时输出count值,对小样本组的结果谨慎解读。
第二,警惕NULL和重复值。聚合函数会自动忽略NULL,但如果NULL占比很高,实际参与计算的样本会明显缩水,可能导致结果失真。可以先用一个子查询统计NULL比例,超过阈值时触发告警。
第三,口径要统一。如果报表同时展示SQL计算的结果和Python计算的结果,务必确认两边用的是样本公式还是总体公式、峰度是否减3。最稳妥的验证方法是拿一组已知分布的数据(比如随机生成的正态样本)测试,偏度应接近0,超额峰度也应接近0,以此校验实现是否正确。
第四,偏度和峰度还可以用于数据质量监控。把每日关键指标的偏度变化率纳入监控,一旦某天偏度突然从0.5飙到3,大概率是出现了异常刷量或数据管道问题,比单纯的均值监控更灵敏。
总结
偏度和峰度是均值、方差之外补齐数据分布画像的两块拼图。如果所用的数据库提供原生SKEWNESS与KURTOSIS聚合函数,直接调用最省事;如果没有,借助窗口函数或两层子查询也能优雅实现。关键在于理解公式口径、保证足够的样本量,并把这两个指标真正用进数据质量监控和业务分析流程中,让SQL不止会算平均数,还能看清分布的形状。
SQL偏度计算SKEWNESS函数KURTOSIS聚合修改时间:2026-09-09 08:28:54