SQL 描述性统计:超越平均值和计数

大多数 SQL 初学者知道如何使用 SUM 计算总数或使用 AVG 计算平均值。但如果你从事数据分析、金融或数据科学工作,基本的平均值往往不够。平均值容易被异常值严重扭曲,而且不能告诉你数据的分布情况。

要真正理解你的数据,你需要描述性统计。在本指南中,我们将超越基础知识,学习如何使用标准 SQL 计算中位数、众数、百分位数和方差。

描述性统计仪表板,显示均值、中位数、众数和标准差

1. 中位数(Median):真正的中间值

平均值(均值)对极端值很敏感。中位数是数据排序后的中间值。它对于房价或员工薪资等数据更加稳健。

虽然一些数据库有内置的 MEDIAN() 函数,但其他数据库需要更多的工作。

PostgreSQL、SQL Server 等数据库的实现

sql 复制代码
-- 使用 percentile_cont(PostgreSQL、SQL Server 等)
SELECT 
  PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median_salary
FROM employees;

SQLite 的实现

在 SQLite(我们在 playground 中使用的数据库)中,我们可以通过排序并选择中间行来计算中位数:

示例数据(employees 表)

employee_id name salary
1 Alice 45000
2 Bob 50000
3 Charlie 55000
4 David 48000
5 Eve 1000000

查询示例

sql 复制代码
-- 在 SQLite 中使用 CTE 计算中位数
WITH sorted_salaries AS (
  SELECT 
    salary,
    ROW_NUMBER() OVER (ORDER BY salary) as row_num,
    COUNT(*) OVER () as total_count
  FROM employees
)
SELECT AVG(salary) as median_salary
FROM sorted_salaries
WHERE row_num IN ((total_count + 1) / 2, (total_count + 2) / 2);

查询结果

median_salary
50000

查询解析

  1. sorted_salaries CTE

    • 使用 ROW_NUMBER() 为排序后的薪资分配行号
    • 使用 COUNT(*) OVER () 获取总行数
  2. 选择中间行

    • 对于奇数行:(total_count + 1) / 2
    • 对于偶数行:(total_count + 1) / 2(total_count + 2) / 2
    • 使用 AVG() 处理偶数行的情况(取两个中间值的平均)

关键洞察

注意平均薪资是 239,000** ,但中位数是 **50,000。中位数更能反映"典型"员工的收入。

为什么会有这么大的差异?

统计量 说明
平均值 $239,000 被 Eve 的 $1,000,000 薪资严重拉高
中位数 $50,000 不受极端值影响,反映真实的中间水平

什么时候使用中位数?

  • 房价分析:豪宅会拉高平均房价
  • 薪资分析:高管薪资会扭曲平均薪资
  • 响应时间分析:极端慢的请求会影响平均响应时间
  • 任何有异常值的数据:中位数更稳健

2. 众数(Mode):最频繁的值

众数是数据集中出现最多的值。这对于回答以下问题很有用:

  • "我们最畅销的产品类别是什么?"
  • "大多数用户住在哪里?"
  • "最常见的错误代码是什么?"

示例数据(sales 表)

sale_id category amount
1 Electronics 500
2 Clothing 200
3 Electronics 300
4 Electronics 450
5 Clothing 150
6 Home 100

查询示例

sql 复制代码
SELECT category, COUNT(*) as frequency
FROM sales
GROUP BY category
ORDER BY frequency DESC
LIMIT 1;

查询结果

category frequency
Electronics 3

结果解读

  • Electronics 是最畅销的产品类别
  • 出现了 3 次(最高频率)
  • 这是我们的众数

扩展查询:查找所有类别的频率

sql 复制代码
SELECT 
  category, 
  COUNT(*) as frequency,
  ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM sales), 2) as percentage
FROM sales
GROUP BY category
ORDER BY frequency DESC;

扩展查询结果

category frequency percentage
Electronics 3 50.00
Clothing 2 33.33
Home 1 16.67

众数的应用场景

  1. 产品分析:识别最受欢迎的产品类别
  2. 用户行为分析:找出最常见的用户操作
  3. 错误分析:识别最频繁的错误类型
  4. 地理分析:找出用户最集中的地区
  5. 设备分析:识别最常用的设备类型

注意事项

  • 众数可能有多个(多峰分布)
  • 对于连续数据(如价格),可能需要先分箱
  • 众数对于分类数据特别有用

3. 百分位数(Percentiles):理解分布

百分位数(如第 90 或第 95 百分位数)对于性能监控(P95 延迟)和成绩评定至关重要。它们告诉你给定百分比的数据低于该值。

什么是百分位数?

  • P25(第一四分位数):25% 的数据低于此值
  • P50(中位数):50% 的数据低于此值
  • P75(第三四分位数):75% 的数据低于此值
  • P90:90% 的数据低于此值
  • P95:95% 的数据低于此值
  • P99:99% 的数据低于此值

示例数据(employees 表,扩展版)

employee_id name salary
1 Alice 45000
2 Bob 50000
3 Charlie 55000
4 David 48000
5 Eve 60000
6 Frank 52000
7 Grace 58000
8 Henry 47000
9 Ivy 53000
10 Jack 49000

查询示例(PostgreSQL)

sql 复制代码
SELECT 
  PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY salary) as p25_salary,
  PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY salary) as p50_salary,
  PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY salary) as p75_salary,
  PERCENTILE_CONT(0.90) WITHIN GROUP (ORDER BY salary) as p90_salary,
  PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY salary) as p95_salary
FROM employees;

查询结果

p25_salary p50_salary p75_salary p90_salary p95_salary
48250 51000 55750 59000 59500

结果解读

  • P25 = $48,250:25% 的员工薪资低于此值
  • P50 = $51,000:50% 的员工薪资低于此值(中位数)
  • P75 = $55,750:75% 的员工薪资低于此值
  • P90 = $59,000:90% 的员工薪资低于此值
  • P95 = $59,500:95% 的员工薪资低于此值

百分位数的应用场景

1. 性能监控

sql 复制代码
-- 计算 API 响应时间的百分位数
SELECT 
  PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY response_time_ms) as p50_latency,
  PERCENTILE_CONT(0.90) WITHIN GROUP (ORDER BY response_time_ms) as p90_latency,
  PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY response_time_ms) as p95_latency,
  PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY response_time_ms) as p99_latency
FROM api_logs
WHERE timestamp >= NOW() - INTERVAL '1 day';

为什么使用 P95 而不是平均值?

指标 说明
平均响应时间 100ms 被少数慢请求拉高
P50(中位数) 50ms 50% 的请求更快
P95 200ms 95% 的请求在 200ms 内完成
P99 500ms 99% 的请求在 500ms 内完成

关键洞察:P95 和 P99 帮助你识别最慢的 5% 和 1% 的请求,这些往往是用户体验的关键。

2. 薪资分析

sql 复制代码
-- 按部门计算薪资百分位数
SELECT 
  department,
  PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY salary) as p25_salary,
  PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY salary) as median_salary,
  PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY salary) as p75_salary
FROM employees
GROUP BY department;

3. 学生成绩分析

sql 复制代码
-- 计算学生成绩的百分位数排名
SELECT 
  student_id,
  score,
  PERCENT_RANK() OVER (ORDER BY score) as percentile_rank
FROM exam_scores;

4. 价格分析

sql 复制代码
-- 识别价格异常值(低于 P5 或高于 P95)
WITH price_percentiles AS (
  SELECT 
    PERCENTILE_CONT(0.05) WITHIN GROUP (ORDER BY price) as p5,
    PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY price) as p95
  FROM products
)
SELECT p.*
FROM products p, price_percentiles pp
WHERE p.price < pp.p5 OR p.price > pp.p95;

跨数据库实现

数据库 百分位数函数
PostgreSQL PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY col)
SQL Server PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY col) OVER ()
Oracle PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY col)
MySQL 需要手动实现(类似 SQLite)
SQLite 需要手动实现(使用 ROW_NUMBER())

4. 范围和分布(Range and Spread)

最简单的分布度量是范围------最大值和最小值之间的差异。虽然简单,但它帮助你识别数据集的边界。

示例数据(products 表)

product_id name price
1 Laptop 1200
2 Mouse 25
3 Keyboard 80
4 Monitor 300
5 Headphones 150

查询示例

sql 复制代码
SELECT 
  MIN(price) as min_price,
  MAX(price) as max_price,
  MAX(price) - MIN(price) as price_range,
  AVG(price) as avg_price,
  PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price) as median_price
FROM products;

查询结果

min_price max_price price_range avg_price median_price
25 1200 1175 351 150

结果解读

  • 最低价格:$25(鼠标)
  • 最高价格:$1,200(笔记本电脑)
  • 价格范围:$1,175(很大的范围)
  • 平均价格:$351(被笔记本电脑拉高)
  • 中位数价格:$150(更能反映典型产品价格)

扩展分析:四分位距(IQR)

四分位距(Interquartile Range, IQR)是 P75 和 P25 之间的差异,它衡量中间 50% 数据的分布范围。

sql 复制代码
WITH quartiles AS (
  SELECT 
    PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY price) as p25,
    PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY price) as p75
  FROM products
)
SELECT 
  p25,
  p75,
  p75 - p25 as iqr,
  p25 - 1.5 * (p75 - p25) as lower_fence,
  p75 + 1.5 * (p75 - p25) as upper_fence
FROM quartiles;

IQR 的用途

  • 识别异常值 :低于 P25 - 1.5 * IQR 或高于 P75 + 1.5 * IQR 的值
  • 箱线图:IQR 是箱线图的核心
  • 稳健的分布度量:不受极端值影响

范围的应用场景

  1. 价格分析:了解产品价格的分布范围
  2. 温度分析:识别温度的变化范围
  3. 库存分析:了解库存水平的波动
  4. 销售分析:识别销售额的变化范围
  5. 质量控制:监控产品规格的变化

5. 标准差和方差(高级内容)

标准差衡量数据与平均值的分散程度。低标准差意味着数据紧密聚集在均值周围,而高标准差意味着数据广泛分散。

数学定义

  • 方差(Variance)AVG((x - mean)^2)
  • 标准差(Standard Deviation)SQRT(方差)

企业数据库的实现

大多数企业数据库(Postgres、Oracle、SQL Server)提供:

  • STDDEV()STDEV()
  • VARIANCE()VAR()

查询示例(PostgreSQL)

sql 复制代码
SELECT 
  AVG(salary) as mean_salary,
  STDDEV(salary) as std_dev_salary,
  VARIANCE(salary) as variance_salary
FROM employees;

查询结果

mean_salary std_dev_salary variance_salary
51700 4932.88 24333333.33

SQLite 的手动实现

SQLite 需要扩展或手动计算:

示例数据(employees 表)

employee_id name salary
1 Alice 45000
2 Bob 50000
3 Charlie 55000
4 David 48000
5 Eve 60000

查询示例

sql 复制代码
-- 在 SQL 中计算标准差和方差
-- (注意:SQLite 需要扩展,但我们可以模拟逻辑)
SELECT 
  AVG(salary) as mean_salary,
  -- 方差 = AVG((x - mean)^2)
  AVG((salary - (SELECT AVG(salary) FROM employees)) * 
      (salary - (SELECT AVG(salary) FROM employees))) as variance,
  -- 标准差 = SQRT(方差)
  SQRT(AVG((salary - (SELECT AVG(salary) FROM employees)) * 
           (salary - (SELECT AVG(salary) FROM employees)))) as std_dev
FROM employees;

查询结果

mean_salary variance std_dev
51600 28640000 5351.40

查询解析

  1. 计算均值AVG(salary) = 51600
  2. 计算每个值与均值的差salary - mean
  3. 计算差的平方(salary - mean)^2
  4. 计算方差AVG((salary - mean)^2)
  5. 计算标准差SQRT(方差)

结果解读

  • 均值:$51,600
  • 方差:28,640,000(单位是平方美元,不直观)
  • 标准差:$5,351.40(与原始数据单位相同,更直观)

标准差的含义

大多数数据(约 68%)落在 均值 ± 1 个标准差 的范围内:

  • 下限:51,600 - 5,351 = $46,249
  • 上限:51,600 + 5,351 = $56,951

标准差的应用场景

1. 质量控制

sql 复制代码
-- 识别超出 2 个标准差的产品(可能有质量问题)
WITH stats AS (
  SELECT 
    AVG(weight) as mean_weight,
    STDDEV(weight) as std_dev_weight
  FROM products
)
SELECT p.*
FROM products p, stats s
WHERE ABS(p.weight - s.mean_weight) > 2 * s.std_dev_weight;

2. 风险评估

sql 复制代码
-- 计算投资组合的风险(标准差)
SELECT 
  portfolio_id,
  AVG(daily_return) as mean_return,
  STDDEV(daily_return) as volatility
FROM portfolio_returns
GROUP BY portfolio_id
ORDER BY volatility DESC;

3. 学生成绩分析

sql 复制代码
-- 识别成绩波动大的学生
SELECT 
  student_id,
  AVG(score) as mean_score,
  STDDEV(score) as score_volatility
FROM exam_scores
GROUP BY student_id
HAVING STDDEV(score) > 10
ORDER BY score_volatility DESC;

4. 销售分析

sql 复制代码
-- 识别销售额波动大的产品
SELECT 
  product_id,
  AVG(daily_sales) as mean_sales,
  STDDEV(daily_sales) as sales_volatility,
  STDDEV(daily_sales) / AVG(daily_sales) as coefficient_of_variation
FROM daily_sales
GROUP BY product_id
ORDER BY coefficient_of_variation DESC;

变异系数(Coefficient of Variation, CV)

CV = 标准差 / 均值

  • 用途:比较不同单位或不同均值的数据的相对变异性
  • 解释:CV 越大,数据的相对变异性越大

描述性统计汇总表

统计量 含义 SQL 关键字/模式 适用场景
Mean(均值) 算术平均值 AVG() 数据无异常值时
Median(中位数) "中间"值 PERCENTILE_CONT(0.5) 有异常值的数据
Mode(众数) 最常见的值 GROUP BY + ORDER BY + LIMIT 1 分类数据
Range(范围) 分布范围(Max - Min) MAX() - MIN() 快速了解边界
IQR(四分位距) P75 - P25 PERCENTILE_CONT(0.75) - PERCENTILE_CONT(0.25) 识别异常值
Std Dev(标准差) 数据分散程度 STDDEV() 评估一致性
Variance(方差) 标准差的平方 VARIANCE() 数学计算
Percentiles(百分位数) 分布位置 PERCENTILE_CONT(p) 性能监控、排名

结论

掌握 SQL 中的统计可以让你直接在数据所在的地方进行复杂的数据分析。无需将大型 CSV 导出到 Excel 或 Python,你可以用几行 SQL 生成强大的洞察。

下次被要求提供"摘要报告"时,不要只提供平均值。包括中位数和范围,以讲述数据的完整故事。

完整的描述性统计查询示例

sql 复制代码
-- 综合描述性统计查询
SELECT 
  -- 中心趋势
  AVG(salary) as mean_salary,
  PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) as median_salary,
  
  -- 分布范围
  MIN(salary) as min_salary,
  MAX(salary) as max_salary,
  MAX(salary) - MIN(salary) as salary_range,
  
  -- 百分位数
  PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY salary) as p25_salary,
  PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY salary) as p75_salary,
  PERCENTILE_CONT(0.90) WITHIN GROUP (ORDER BY salary) as p90_salary,
  
  -- 分散程度
  STDDEV(salary) as std_dev_salary,
  VARIANCE(salary) as variance_salary,
  
  -- 样本量
  COUNT(*) as sample_size
FROM employees;

最佳实践

  1. 选择合适的统计量

    • 有异常值?使用中位数而不是均值
    • 分类数据?使用众数
    • 需要了解分布?使用百分位数
    • 评估一致性?使用标准差
  2. 组合使用多个统计量

    • 均值 + 中位数:了解是否有偏斜
    • 范围 + 标准差:了解分布的宽度
    • 百分位数:了解完整的分布情况
  3. 可视化辅助

    • 箱线图:显示中位数、四分位数、范围
    • 直方图:显示分布形状
    • 散点图:显示数据点的分布
  4. 注意数据库差异

    • 不是所有数据库都支持所有函数
    • SQLite 需要手动实现或扩展
    • 查阅数据库文档了解具体语法

实践建议

使用上面的 playground 代码片段,添加更多"异常值"薪资,看看平均值如何变化,而中位数保持稳定!

实验示例

sql 复制代码
-- 添加一个极端高薪
INSERT INTO employees (name, salary) VALUES ('CEO', 5000000);

-- 重新计算统计量
SELECT 
  AVG(salary) as mean_salary,
  PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) as median_salary
FROM employees;

观察:

  • 均值会大幅增加
  • 中位数几乎不变

这就是为什么中位数更稳健!


相关文章推荐


本文转载自 www.hisqlboy.com

原文标题:Descriptive Statistics in SQL: Beyond Average and Count

原文链接:https://www.hisqlboy.com/blog/descriptive-statistics-in-sql

原作者:SQL Boy Team

转载日期:2026-02-12

著作权归原作者所有。本文仅用于学习交流,非商业用途。

相关推荐
ActionTech2 小时前
数据集推荐 06 | 首款 NL2GeoSQL 的测试基准和数据集来了!
数据库·人工智能·sql
码云数智-大飞2 小时前
跳出索引思维定式:一次基于业务逻辑的非典型 SQL 优化实践
数据库·sql
PD我是你的真爱粉2 小时前
Redis基础与数据结构
数据结构·数据库·redis
山岚的运维笔记2 小时前
SQL Server笔记 -- 第46章 窗口函数
数据库·笔记·sql·microsoft·sqlserver
科技块儿2 小时前
【工具对比】免费IP库用于广告投放是否可靠?误差率实测报告
网络·数据库·tcp/ip
晔子yy2 小时前
带你了解Java中的Mono接口
java·数据库·oracle
全栈前端老曹2 小时前
【Redis】发布订阅模型 —— Pub/Sub 原理、消息队列、聊天系统实战
前端·数据库·redis·设计模式·node.js·全栈·发布订阅模型
SQL必知必会2 小时前
使用 SQL 构建转化漏斗
数据库·sql·数据分析
丿BAIKAL巛2 小时前
Docker部署的Mysql数据库自动化备份
数据库·mysql·docker