一、问题拆解:从业务需求到SQL逻辑
1.1 需求分析
题目要求:计算浙江大学用户在不同难度题目下的答题正确率,并按正确率升序排序。
关键分析点:
- 数据来源:
user_profile
:存储用户信息(大学)question_practice_detail
:存储答题记录question_detail
:存储题目难度信息
- 筛选条件:
university = '浙江大学'
- 计算逻辑:
- 正确率 = 正确答题数 / 总答题数
- 需按题目难度分组(
difficult_level
)
- 结果要求:按正确率升序排列
1.2 示例数据与预期结果
假设各表结构及部分数据:
user_profile:
device_id | university |
---|---|
1001 | 浙江大学 |
1002 | 浙江大学 |
1003 | 复旦大学 |
question_practice_detail:
device_id | question_id | result |
---|---|---|
1001 | Q101 | right |
1001 | Q102 | wrong |
1002 | Q102 | right |
1002 | Q103 | right |
question_detail:
question_id | difficult_level |
---|---|
Q101 | hard |
Q102 | medium |
Q103 | easy |
预期结果:
difficult_level | correct_rate |
---|---|
medium | 0.5000 |
hard | 1.0000 |
easy | 1.0000 |
二、核心SQL解析:多表关联与聚合计算
2.1 完整SQL语句
SELECT qd.difficult_level, ROUND(SUM(CASE WHEN qpd.result = 'right' THEN 1 ELSE 0 END) / COUNT(qpd.question_id), 4) AS correct_rate
FROM user_profile AS up
INNER JOIN question_practice_detail AS qpd ON up.device_id = qpd.device_id
INNER JOIN question_detail AS qd ON qpd.question_id = qd.question_id
WHERE up.university = '浙江大学'
GROUP BY qd.difficult_level
ORDER BY correct_rate ASC;
2.2 关键技术点分解
1. 三表关联策略
user_profile INNER JOIN question_practice_detail INNER JOIN question_detail
- 作用:
- 通过
device_id
关联用户与答题记录 - 通过
question_id
关联答题记录与题目难度
- 通过
- 选择内连接的原因:
- 仅统计实际答题的用户和题目
- 排除未答题用户或无难度信息的题目
2. 正确率计算逻辑
ROUND(SUM(CASE WHEN qpd.result = 'right' THEN 1 ELSE 0 END) / COUNT(qpd.question_id), 4)
- 分子:
SUM(CASE ...)
统计正确答题数 - 分母:
COUNT(qpd.question_id)
统计总答题数 - ROUND函数:保留四位小数
3. 分组与排序
GROUP BY qd.difficult_level
ORDER BY correct_rate ASC
- 分组:按题目难度(
difficult_level
)分组 - 排序:按计算出的正确率升序排列
三、SQL语法精讲
3.1 多表连接语法
table1 [INNER|LEFT|RIGHT] JOIN table2 ON condition1
[INNER|LEFT|RIGHT] JOIN table3 ON condition2
- 本例连接类型:内连接(INNER JOIN)
- 执行顺序:从左到右依次连接各表
3.2 聚合函数详解
函数 | 作用 |
---|---|
SUM(expr) | 计算表达式的总和 |
COUNT(expr) | 统计非NULL值的数量 |
ROUND(num, dec) | 将数值四舍五入到指定小数位数 |
3.3 CASE表达式
CASE WHEN condition THEN value ELSE default_value
END
- 本例应用:将布尔条件(
result='right'
)转换为数值(1或0)
四、执行流程与数据流转
4.1 分步执行过程
步骤1:筛选浙江大学用户
WHERE up.university = '浙江大学'
- 结果:保留
user_profile
中浙江大学的用户记录
步骤2:连接答题记录表
INNER JOIN question_practice_detail AS qpd
ON up.device_id = qpd.device_id
- 结果:
device_id university question_id result 1001 浙江大学 Q101 right 1001 浙江大学 Q102 wrong 1002 浙江大学 Q102 right 1002 浙江大学 Q103 right
步骤3:连接题目难度表
INNER JOIN question_detail AS qd
ON qpd.question_id = qd.question_id
- 结果:
device_id university question_id result difficult_level 1001 浙江大学 Q101 right hard 1001 浙江大学 Q102 wrong medium 1002 浙江大学 Q102 right medium 1002 浙江大学 Q103 right easy
步骤4:分组与聚合计算
- 按难度分组:
hard
:Q101(1条记录,1正确)medium
:Q102(2条记录,1正确)easy
:Q103(1条记录,1正确)
- 计算正确率:
hard
:1/1 = 1.0000medium
:1/2 = 0.5000easy
:1/1 = 1.0000
步骤5:排序
ORDER BY correct_rate ASC
- 最终结果:按正确率升序排列
五、性能优化策略
5.1 索引优化
-- 为user_profile添加索引
CREATE INDEX idx_university_device ON user_profile(university, device_id);-- 为question_practice_detail添加复合索引
CREATE INDEX idx_device_question ON question_practice_detail(device_id, question_id, result);-- 为question_detail添加索引
CREATE INDEX idx_question_difficulty ON question_detail(question_id, difficult_level);
5.2 执行计划分析
使用EXPLAIN
关键字分析执行计划:
EXPLAIN
SELECT ... (原SQL) ...;
关键指标解读:
type
列:期望各表连接类型为ref
或eq_ref
key
列:应显示使用了上述创建的索引Extra
列:避免出现Using filesort
和Using temporary
六、常见问题与解决方案
6.1 除数为零问题
问题:若某难度题目无人作答,COUNT(qpd.question_id)
为0,导致除零错误。
解决方案:
-- 使用NULLIF防止除零
ROUND(SUM(CASE WHEN qpd.result = 'right' THEN 1 ELSE 0 END) / NULLIF(COUNT(qpd.question_id), 0), 4)-- 若存在无答题记录的难度,需改用LEFT JOIN并处理NULL
6.2 正确率精度问题
问题:直接相除可能导致精度丢失。
解决方案:
-- 显式转换为DECIMAL类型
ROUND(SUM(CASE WHEN qpd.result = 'right' THEN 1 ELSE 0 END) / COUNT(qpd.question_id), 4) AS correct_rate
6.3 多表连接性能问题
问题:大数据量下多表连接可能性能较差。
解决方案:
- 确保连接字段都有索引
- 优先过滤数据(如先筛选浙江大学用户)
- 考虑使用临时表存储中间结果
七、扩展应用:分组聚合的进阶场景
7.1 计算各大学平均正确率
SELECT up.university,ROUND(SUM(CASE WHEN qpd.result = 'right' THEN 1 ELSE 0 END) / COUNT(qpd.question_id), 4) AS avg_correct_rate
FROM ...
GROUP BY up.university
ORDER BY avg_correct_rate DESC;
7.2 按难度和用户分组
SELECT up.device_id,qd.difficult_level,ROUND(SUM(CASE WHEN qpd.result = 'right' THEN 1 ELSE 0 END) / COUNT(qpd.question_id), 4) AS user_difficulty_rate
FROM ...
GROUP BY up.device_id, qd.difficult_level;
7.3 复杂条件筛选
WHERE up.university IN ('浙江大学', '复旦大学')AND qd.difficult_level IN ('hard', 'medium')AND qpd.date >= '2021-08-01';
八、总结与技术要点
8.1 核心技术点回顾
- 多表关联策略:通过内连接整合三张表的数据
- 分组聚合技巧:
- 使用
SUM(CASE ...)
统计条件计数 - 使用
COUNT
统计总数 - 使用
ROUND
控制结果精度
- 使用
- 排序与过滤:合理使用
WHERE
、GROUP BY
和ORDER BY
- 索引优化:为连接字段和过滤条件创建复合索引
8.2 技术决策树
开始
│
├── 是否需要多表关联?
│ │
│ └── 是 → 选择合适的连接类型(INNER/LEFT)
│ │
│ ├── 仅保留匹配记录 → INNER JOIN
│ │
│ └── 保留主表所有记录 → LEFT JOIN
│
├── 是否需要分组统计?
│ │
│ └── 是 → 确定分组字段和聚合函数
│ │
│ ├── 统计总数 → COUNT
│ │
│ ├── 统计条件数 → SUM(CASE ...)
│ │
│ └── 计算比例 → SUM(...)/COUNT(...)
│
├── 是否存在性能问题?
│ │
│ └── 是 → 为JOIN字段和WHERE条件创建复合索引
│
└── 结束
通过深入理解多表关联和分组聚合的原理,结合合理的索引优化,你可以高效处理各种复杂的统计分析需求,避免常见的SQL性能陷阱,提升数据处理能力。