大多数 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 |
查询解析:
-
sorted_salaries CTE:
- 使用
ROW_NUMBER()为排序后的薪资分配行号 - 使用
COUNT(*) OVER ()获取总行数
- 使用
-
选择中间行:
- 对于奇数行:
(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 |
众数的应用场景:
- 产品分析:识别最受欢迎的产品类别
- 用户行为分析:找出最常见的用户操作
- 错误分析:识别最频繁的错误类型
- 地理分析:找出用户最集中的地区
- 设备分析:识别最常用的设备类型
注意事项:
- 众数可能有多个(多峰分布)
- 对于连续数据(如价格),可能需要先分箱
- 众数对于分类数据特别有用
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 是箱线图的核心
- 稳健的分布度量:不受极端值影响
范围的应用场景:
- 价格分析:了解产品价格的分布范围
- 温度分析:识别温度的变化范围
- 库存分析:了解库存水平的波动
- 销售分析:识别销售额的变化范围
- 质量控制:监控产品规格的变化
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 |
查询解析:
- 计算均值 :
AVG(salary)= 51600 - 计算每个值与均值的差 :
salary - mean - 计算差的平方 :
(salary - mean)^2 - 计算方差 :
AVG((salary - mean)^2) - 计算标准差 :
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;
最佳实践
-
选择合适的统计量:
- 有异常值?使用中位数而不是均值
- 分类数据?使用众数
- 需要了解分布?使用百分位数
- 评估一致性?使用标准差
-
组合使用多个统计量:
- 均值 + 中位数:了解是否有偏斜
- 范围 + 标准差:了解分布的宽度
- 百分位数:了解完整的分布情况
-
可视化辅助:
- 箱线图:显示中位数、四分位数、范围
- 直方图:显示分布形状
- 散点图:显示数据点的分布
-
注意数据库差异:
- 不是所有数据库都支持所有函数
- 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;
观察:
- 均值会大幅增加
- 中位数几乎不变
这就是为什么中位数更稳健!
相关文章推荐
- Calculating Percentiles and Median in SQL - AVG 告诉你均值,但中位数和百分位数呢?
- Solving the Gaps and Islands Problem in SQL - 掌握最著名的 SQL 面试问题之一:识别连续序列
- SQL for Data Analysis: The Ultimate Guide - 超越基础 SELECT,掌握真实世界数据分析的核心 SQL 技术
本文转载自 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