【牛客网】非技术快速入门题库
- 【牛客网】非技术快速入门题库 SQL 篇
- 1.查询所有列
- 2.查询多
- 3.查询结果去重
- 4.查询结果限制返回行数
- 5.将查询后的列重新命名
- 6.查找学校是北大的学生信息
- 7.查找年龄大于24岁的用户信息
- 8.查找某个年龄段的用户信息
- 9.查找除复旦大学的用户信息
- 10.用where过滤空值练习
- 11.高级操作符练习(1)
- 12.高级操作符练习(2)
- 13.Where in 和 Not in
- 14.操作符混合运用
- 15.查看学校名称中含北京的用户
- 16.查找GPA最高值
- 17.计算男生人数以及平均GPA
- 18.分组计算练习题
- 19.分组过滤练习题
- 20.分组排序练习题
- 21.浙江大学用户题目回答情况
- 22.统计每个学校的答过题的用户的平均答题数
- 23.统计每个学校各难度的用户平均刷题数
- 24.统计每个用户的平均刷题数
- 25.查找山东大学或者性别为男生的信息
- 26.计算25岁以上和以下的用户数量
- 27.查看不同年龄段的用户明细
- 28.计算用户8月每天的练题数量
- 29. 计算用户的平均次日留存率
- 30.统计每种性别的人数
- 31.提取博客URL中的用户名
- 32.截取出年龄
- 33.找出每个学校GPA最低的同学
- 34.统计复旦用户8月练题情况
- 35.浙大不同难度题目的正确率
- 36. 查找后排序
- 37.查找后多列排序
- 38.查找后降序排列
- 39.21年8月份练题总数
【牛客网】非技术快速入门题库 SQL 篇
1.查询所有列
-- 直接 select 所有的列就行,可以用 * ,但一般不建议直接用select *
-- 查询 user_profile 表中的所有记录和所有字段
SELECT *
FROM user_profile; -- 从用户资料表(user_profile)中选择所有数据
2.查询多
## 从用户资料表中查询设备ID、性别、年龄和大学信息
SELECT
device_id, -- 用户的设备ID
gender, -- 用户的性别
age, -- 用户的年龄
university -- 用户所在的大学
FROM user_profile -- 数据来源于user_profile表,该表存储了用户的个人信息
; -- 选择user_profile表中的device_id, gender, age, university字段的所有记录
3.查询结果去重
# 方法一
-- 从用户资料表中查询所有不同的大学信息
SELECT DISTINCT
university -- 用户所在的大学名称
FROM user_profile -- 数据来源于user_profile表,该表存储了用户的个人信息
; -- 选择user_profile表中的university字段,并去除重复项,仅保留不同的值
# 方法二
-- 从用户资料表中按第一个SELECT表达式(即university字段)分组,查询所有大学信息
SELECT
university -- 用户所在的大学名称
FROM user_profile -- 数据来源于user_profile表,该表存储了用户的个人信息
GROUP BY 1 -- 根据SELECT子句中的第一个字段(即university)对结果进行分组。这里的"1"是对SELECT列表中第一项的引用
; -- 这个查询会返回每个大学的一条记录,类似于使用DISTINCT的效果
# 其中 1 代表 select 后的第一个字段
4.查询结果限制返回行数
## 从用户资料表中查询设备ID,并限制结果从第1行开始的2行记录
SELECT
device_id -- 用户的设备ID
FROM user_profile -- 数据来源于user_profile表,该表存储了用户的个人信息
LIMIT 0, 2 -- 从第1条记录开始(索引为0),获取2条记录
; -- 这个查询会返回user_profile表中device_id字段的前两条记录
5.将查询后的列重新命名
## 从用户资料表中查询设备ID,并将结果列命名为user_infos_example,限制结果为前2行记录
SELECT
device_id AS user_infos_example -- 用户的设备ID,并将此列重命名为user_infos_example
FROM user_profile -- 数据来源于user_profile表,该表存储了用户的个人信息
LIMIT 2 -- 仅获取查询结果的前2条记录
; -- 这个查询会返回user_profile表中device_id字段的前两条记录,并将其显示为user_infos_example列
6.查找学校是北大的学生信息
## 从用户资料表中查询设备ID和大学信息,并筛选出大学为北京大学的记录
SELECT
device_id, -- 用户的设备ID
university -- 用户所在的大学名称
FROM user_profile -- 数据来源于user_profile表,该表存储了用户的个人信息
WHERE university = '北京大学' -- 筛选条件:仅选择university字段值为'北京大学'的记录
; -- 这个查询会返回user_profile表中所有属于北京大学的用户的设备ID和大学名称
7.查找年龄大于24岁的用户信息
## 从用户资料表中查询设备ID、性别、年龄和大学信息,并筛选出年龄大于24岁的用户记录
SELECT
device_id, -- 用户的设备ID
gender, -- 用户的性别
age, -- 用户的年龄
university -- 用户所在的大学名称
FROM user_profile -- 数据来源于user_profile表,该表存储了用户的个人信息
WHERE age > 24 -- 筛选条件:仅选择age字段值大于24的记录
; -- 这个查询会返回user_profile表中所有年龄大于24岁的用户的设备ID、性别、年龄及大学名称
8.查找某个年龄段的用户信息
## 从用户资料表中查询设备ID、性别和年龄,并筛选出年龄在20到23岁之间的用户记录
SELECT
device_id, -- 用户的设备ID
gender, -- 用户的性别
age -- 用户的年龄
FROM user_profile -- 数据来源于user_profile表,该表存储了用户的个人信息
WHERE age >= 20 AND age <= 23 -- 筛选条件:仅选择age字段值在20至23岁(包括20和23岁)之间的记录
; -- 这个查询会返回user_profile表中所有年龄介于20到23岁之间的用户的设备ID、性别及年龄
9.查找除复旦大学的用户信息
## 从用户资料表中查询设备ID、性别、年龄和大学信息,并筛选出大学不是复旦大学的用户记录
SELECT
device_id, -- 用户的设备ID
gender, -- 用户的性别
age, -- 用户的年龄
university -- 用户所在的大学名称
FROM user_profile -- 数据来源于user_profile表,该表存储了用户的个人信息
WHERE university != '复旦大学' -- 筛选条件:仅选择university字段值不等于'复旦大学'的记录
; -- 这个查询会返回user_profile表中所有不属于复旦大学的用户的设备ID、性别、年龄及大学名称
10.用where过滤空值练习
## 从用户资料表中查询设备ID、性别、年龄和大学信息,并筛选出年龄字段非空的用户记录
SELECT
device_id, -- 用户的设备ID
gender, -- 用户的性别
age, -- 用户的年龄
university -- 用户所在的大学名称
FROM user_profile -- 数据来源于user_profile表,该表存储了用户的个人信息
WHERE age IS NOT NULL -- 筛选条件:仅选择age字段值非空(即已填写年龄信息)的记录
; -- 这个查询会返回user_profile表中所有有提供年龄信息的用户的设备ID、性别、年龄及大学名称
11.高级操作符练习(1)
## 从用户资料表中查询设备ID、性别、年龄、大学和GPA信息,并筛选出GPA大于3.5且性别为男性的用户记录
SELECT
device_id, -- 用户的设备ID
gender, -- 用户的性别
age, -- 用户的年龄
university, -- 用户所在的大学名称
gpa -- 用户的平均成绩点数(GPA)
FROM user_profile -- 数据来源于user_profile表,该表存储了用户的个人信息
WHERE gpa > 3.5 AND gender = 'male' -- 筛选条件:仅选择gpa字段值大于3.5且gender字段值为'male'的记录
; -- 这个查询会返回user_profile表中所有GPA大于3.5并且性别为男性的用户的设备ID、性别、年龄、大学名称及GPA
12.高级操作符练习(2)
-- 从用户资料表中查询设备ID、性别、年龄、大学和GPA信息,并筛选出大学为北京大学或GPA大于3.7的用户记录
SELECT
device_id, -- 用户的设备ID
gender, -- 用户的性别
age, -- 用户的年龄
university, -- 用户所在的大学名称
gpa -- 用户的平均成绩点数(GPA)
FROM user_profile -- 数据来源于user_profile表,该表存储了用户的个人信息
WHERE university = '北京大学' OR gpa > 3.7 -- 筛选条件:选择university字段值为'北京大学'或者gpa字段值大于3.7的记录
; -- 这个查询会返回user_profile表中所有大学为北京大学或者GPA大于3.7的用户的设备ID、性别、年龄、大学名称及GPA
13.Where in 和 Not in
-- 从用户资料表中查询设备ID、性别、年龄、大学和GPA信息,并筛选出特定大学(北京大学、复旦大学、山东大学)的用户记录
SELECT
device_id, -- 用户的设备ID
gender, -- 用户的性别
age, -- 用户的年龄
university, -- 用户所在的大学名称
gpa -- 用户的平均成绩点数(GPA)
FROM user_profile -- 数据来源于user_profile表,该表存储了用户的个人信息
WHERE university IN ('北京大学', '复旦大学', '山东大学') -- 筛选条件:仅选择university字段值在指定列表内的记录,即'北京大学', '复旦大学', 或'山东大学'
; -- 这个查询会返回user_profile表中所有属于北京大学、复旦大学或山东大学的用户的设备ID、性别、年龄、大学名称及GPA
14.操作符混合运用
-- 查询符合条件的学生信息,包括设备ID、性别、年龄、大学和GPA
SELECT
device_id -- 设备ID
,gender -- 性别
,age -- 年龄
,university -- 大学
,gpa -- GPA(平均成绩)
FROM user_profile
WHERE
-- 筛选条件:对于山东大学的学生,要求GPA大于3.5
(gpa > 3.5 AND university = '山东大学')
OR
-- 对于复旦大学的学生,要求GPA大于3.8
(gpa > 3.8 AND university = '复旦大学')
-- 结果按照设备ID升序排列
ORDER BY device_id ASC;
15.查看学校名称中含北京的用户
-- 查询学校名称中包含“北京”的用户设备ID、年龄和学校名称
SELECT
device_id, -- 设备ID
age, -- 年龄
university -- 学校名称
FROM user_profile -- 从用户信息表中获取数据
WHERE university LIKE '%北京%'; -- 筛选出学校名称中包含“北京”的用户
# _:匹配任意一个字符;
# %:匹配 0 个或多个字符;
# [ ]:匹配 [ ] 中的任意一个字符(若要比较的字符是连续的,则可以用连字符 “-” 表达 );
# [^ ]:不匹配 [ ] 中的任意一个字符。
16.查找GPA最高值
# 方法一
## 查询复旦大学学生的最高GPA,并将结果四舍五入保留1位小数
SELECT
ROUND(MAX(gpa), 1) AS gpa -- 计算并格式化最高GPA
FROM user_profile -- 从用户信息表中获取数据
WHERE university = '复旦大学'; -- 筛选出复旦大学的学生
# 方法二
## 查询复旦大学学生的GPA信息,并获取GPA最高的学生
SELECT
gpa -- 平均成绩(GPA)
FROM user_profile -- 来自用户资料表
WHERE
university = '复旦大学' -- 筛选条件:仅选择大学为复旦大学的学生记录
ORDER BY gpa DESC -- 根据GPA成绩从高到低排序
LIMIT 1 -- 限制结果数量为1条,即只返回GPA最高的那一条记录
;
# 先筛选后排序,显示1列
# 方法三
## 查询复旦大学 GPA 最高的学生的 GPA
SELECT gpa
FROM (
-- 子查询:为每个复旦大学的学生按 GPA 降序排名
SELECT gpa,
ROW_NUMBER() OVER (PARTITION BY university ORDER BY gpa DESC) AS ranking
FROM user_profile
WHERE university = '复旦大学' -- 筛选出复旦大学的学生
) AS t -- 将子查询结果命名为 t
WHERE t.ranking = 1; -- 筛选出排名为 1 的学生(即 GPA 最高的学生)
# 这里使用的是窗口排序函数加子查询解题
# 用 row_number()over() 在学校组里对GPA进行倒序排序,再增加条件 ranking=1 此时为GPA最大值
17.计算男生人数以及平均GPA
## 统计男性用户的数量和平均 GPA
SELECT
COUNT(1) AS male_num, -- 统计满足条件的行数
AVG(gpa) AS avg_gpa -- 计算男性用户的平均 GPA
FROM user_profile -- 从 user_profile 表中获取数据
WHERE gender = 'male'; -- 筛选出性别为男性的用户
18.分组计算练习题
-- 问题分解:
-- 限定条件:无;
-- 每个学校每种性别:按学校和性别分组:`group by gender, university`
-- 用户数:count(device_id)
-- 30天内平均活跃天数:avg(active_days_within_30)
-- 平均发帖数量:avg(question_cnt)
## 按性别和学校分组,统计每组的用户数、平均活跃天数和平均提问次数
SELECT
gender, -- 性别
university, -- 学校
COUNT(DISTINCT device_id) AS user_num, -- 统计每组的独立用户数
ROUND(AVG(active_days_within_30), 1) AS avg_active_day, -- 计算平均活跃天数,保留1位小数
ROUND(AVG(question_cnt), 1) AS avg_question_cnt -- 计算平均提问次数,保留1位小数
FROM user_profile -- 从 user_profile 表中获取数据
GROUP BY gender, university -- 按性别和学校分组
ORDER BY gender, university; -- 按性别和学校排序
-- 题目改变,需按照性别排序,然后在相同性别内按照大学名称排序
19.分组过滤练习题
-- 问题分解:
-- 限定条件:平均发贴数低于5或平均回帖数小于20的学校,`avg(question_cnt)<5 or avg(answer_cnt)<20`,聚合函数结果作为筛选条件时,不能用where,而是用having语法,配合重命名即可;
-- 按学校输出:需要对每个学校统计其平均发贴数和平均回帖数,因此`group by university`
# 方法一
## 按学校分组,统计每组的平均提问次数和平均回答次数,并筛选出平均提问次数小于5或平均回答次数小于20的学校
SELECT
university, -- 学校
ROUND(AVG(question_cnt), 3) AS avg_question_cnt, -- 计算平均提问次数,保留3位小数
ROUND(AVG(answer_cnt), 3) AS avg_answer_cnt -- 计算平均回答次数,保留3位小数
FROM user_profile -- 从 user_profile 表中获取数据
GROUP BY university -- 按学校分组
HAVING avg_question_cnt < 5 OR avg_answer_cnt < 20; -- 筛选出平均提问次数小于5或平均回答次数小于20的学校
# 方法二
## 查询平均提问次数小于5或平均回答次数小于20的学校
SELECT *
FROM (
-- 子查询:按学校分组,计算每组的平均提问次数和平均回答次数
SELECT
university, -- 学校
ROUND(AVG(question_cnt), 3) AS avg_question_cnt, -- 计算平均提问次数,保留3位小数
ROUND(AVG(answer_cnt), 3) AS avg_answer_cnt -- 计算平均回答次数,保留3位小数
FROM user_profile -- 从 user_profile 表中获取数据
GROUP BY university -- 按学校分组
) AS a -- 将子查询结果命名为 a
WHERE avg_question_cnt < 5 OR avg_answer_cnt < 20; -- 筛选出平均提问次数小于5或平均回答次数小于20的学校
20.分组排序练习题
-- 问题分解:
-- 限定条件:无;
-- 不同大学:按学校分组`group by university`
-- 平均发帖数:`avg(question_cnt)`
-- 升序排序:`order by avg_question_cnt`
## 按学校分组,计算每组的平均发帖次数,并按平均发帖次数升序排序
SELECT
university, -- 学校名称
AVG(question_cnt) AS avg_question_cnt -- 计算平均发帖次数
FROM user_profile -- 从用户信息表中获取数据
GROUP BY university -- 按学校分组
ORDER BY avg_question_cnt; -- 按平均发帖次数升序排序(从小到大)
21.浙江大学用户题目回答情况
-- 问题分解:
-- 限定条件:来自浙江大学的用户,学校信息在用户画像表,答题情况在用户练习明细表,因此需要通过device_id关联两个表的数据;
-- 方法一:join两个表,用inner join,条件是`on up.device_id=qpd.device_id and up.university='浙江大学'`
-- 方法二:先从画像表找到浙江大学的所有学生id列表`where university='浙江大学'`,再去练习明细表筛选出id在这个列表的记录,用where in
-- 方法三:子查询获取用户设备ID和学校信息,按设备 ID 连接答题记录表,再去练习明细表筛选出id在这个列表的记录,用where in
# 方法一
## 查询浙江大学学生的练习详情,包括设备ID、问题ID和结果,并按问题ID排序
SELECT
qpd.device_id, -- 设备ID
qpd.question_id, -- 问题ID
qpd.result -- 练习结果
FROM question_practice_detail AS qpd -- 来自练习详情表(question_practice_detail)
INNER JOIN user_profile AS up -- 内连接用户资料表(user_profile),以获取用户的个人信息
ON up.device_id = qpd.device_id AND up.university = '浙江大学' -- 连接条件:用户资料表中的device_id与练习详情表中的device_id匹配,并且大学为浙江大学
ORDER BY question_id -- 根据问题ID字段进行排序,确保结果按问题ID升序排列
;
# 方法二
## 查询浙江大学的用户答题记录,并按题目ID排序
SELECT
a.device_id AS device_id, -- 设备ID
b.question_id AS question_id, -- 题目ID
b.result AS result -- 答题结果
FROM user_profile AS a -- 用户信息表
LEFT JOIN question_practice_detail AS b -- 答题记录表
ON a.device_id = b.device_id -- 按设备ID连接两个表
WHERE a.university = '浙江大学' -- 筛选出浙江大学的用户
ORDER BY 2; -- 按第2列(question_id)排序
# 方法三
## 查询浙江大学的用户答题记录,并按题目ID排序
SELECT
a.device_id, -- 设备ID
a.question_id, -- 题目ID
a.result -- 答题结果
FROM question_practice_detail AS a -- 答题记录表
LEFT JOIN (
-- 子查询:获取用户设备ID和学校信息
SELECT
device_id, -- 设备ID
university -- 学校
FROM user_profile -- 用户信息表
) AS b -- 将子查询结果命名为 b
ON a.device_id = b.device_id -- 按设备ID连接两个表
WHERE b.university = '浙江大学' -- 筛选出浙江大学的用户
ORDER BY a.question_id; -- 按题目ID排序
22.统计每个学校的答过题的用户的平均答题数
-- 问题分解:
-- 限定条件:无;
-- 每个学校:按学校分组,`group by university`
-- 平均答题数量:在每个学校的分组内,用总答题数量除以总人数即可得到平均答题数量`count(question_id) / count(distinct device_id)`。
-- 表连接:学校和答题信息在不同的表,需要做连接
-- 精度:保留4位小数round(x, 4)
## 按学校分组,计算每个学校的平均答题次数,并按学校名称排序
SELECT
a.university, -- 学校名称
round(COUNT(b.question_id) / COUNT(DISTINCT b.device_id), 4) AS avg_answer_cnt -- 计算平均答题次数
FROM user_profile AS a -- 用户信息表
RIGHT JOIN question_practice_detail AS b -- 答题记录表
ON a.device_id = b.device_id -- 按设备ID连接两个表
GROUP BY a.university -- 按学校分组
ORDER BY a.university; -- 按学校名称排序
23.统计每个学校各难度的用户平均刷题数
-- 问题分解:
-- 限定条件:无;
-- 每个学校:按学校分组`group by university`
-- 不同难度:按难度分组`group by difficult_level`
-- 平均答题数:总答题数除以总人数`count(qpd.question_id) / count(distinct qpd.device_id)`
-- 来自上面信息三个表,需要联表,up与qpd用device_id连接,qd与qpd用question_id连接。
-- 平均值精度:保留4位小数round(x, 4)
-- 计算每个大学每种难度级别的平均答题数
SELECT
up.university AS university, -- 大学名称
qd.difficult_level AS difficult_level, -- 难度级别
-- 计算每个大学每种难度级别的平均答题数:总答题次数除以不同设备ID的数量(即用户数量)
round(COUNT(up.university) / COUNT(DISTINCT qpd.device_id),4) AS avg_answer_cnt
FROM
question_practice_detail AS qpd -- 练习详情表
LEFT JOIN
user_profile AS up ON qpd.device_id = up.device_id -- 通过device_id与用户资料表关联
LEFT JOIN
question_detail AS qd ON qpd.question_id = qd.question_id -- 假设通过question_id与问题详情表关联
GROUP BY
up.university, qd.difficult_level -- 按大学和难度级别分组
;
24.统计每个用户的平均刷题数
-- 问题分解:
-- 限定条件:where university = '山东大学';
-- 每个学校:按学校分组`group by university`
-- 不同难度:按难度分组`group by difficult_level`
-- 平均刷题数:总刷题数除以总人数`count(qpd.question_id) / count(distinct qpd.device_id)`
-- 来自上面信息三个表,需要联表,up与qpd用device_id连接,qd与qpd用question_id连接。
-- 平均值精度:保留4位小数round(x, 4)
# 方法一
## 查询山东大学学生在不同难度级别的平均答题数量
SELECT
up.university, -- 大学名称
qd.difficult_level, -- 难度级别
COUNT(qpd.question_id) / COUNT(DISTINCT qpd.device_id) AS avg_answer_cnt -- 计算每个难度级别的平均答题数
FROM
question_practice_detail AS qpd -- 练习详情表
LEFT JOIN
user_profile AS up ON qpd.device_id = up.device_id -- 通过device_id与用户资料表关联
LEFT JOIN
question_detail AS qd ON qpd.question_id = qd.question_id -- 假设通过question_id与问题详情表关联
WHERE
up.university = '山东大学' -- 筛选条件:仅选择大学为山东大学的学生记录
GROUP BY
up.university, qd.difficult_level -- 按大学和难度级别分组
;
# 方法二
## 查询山东大学学生在不同难度级别的平均答题数量
SELECT
b.university AS university, -- 用户所在大学名称
c.difficult_level AS difficult_level, -- 题目的难度级别
COUNT(1) / COUNT(DISTINCT b.device_id) AS avg_answer_cnt -- 计算每个难度级别的平均答题数(总答题次数除以不同用户的数量)
FROM
question_practice_detail a -- 练习详情表
LEFT JOIN
user_profile b ON a.device_id = b.device_id AND b.university = '山东大学' -- 通过device_id与用户资料表关联,并筛选出大学为山东大学的记录
LEFT JOIN
question_detail c ON a.question_id = c.question_id -- 通过question_id与问题详情表关联
WHERE
b.device_id IS NOT NULL -- 确保只选择有对应用户信息的记录
GROUP BY
1, 2 -- 按第一个和第二个SELECT项分组(即按university和difficult_level分组)
;
25.查找山东大学或者性别为男生的信息
-- 问题分解:
-- 限定条件:学校为山东大学或者性别为男性的用户:`university='山东大学'`, `gender='male'`;
-- 分别查看&结果不去重:所以直接使用两个条件的 or 是不行的,直接用 union 也不行,要用union all,分别去查满足条件1的和满足条件2的,然后合在一起不去重
-- 查询山东大学的所有学生信息
SELECT
device_id, -- 设备ID
gender, -- 性别
age, -- 年龄
gpa -- GPA(平均成绩)
FROM user_profile -- 来自用户资料表
WHERE university = '山东大学' -- 筛选条件:仅选择大学为山东大学的学生记录
UNION ALL
-- 查询所有男性学生的信息
SELECT
device_id, -- 设备ID
gender, -- 性别
age, -- 年龄
gpa -- GPA(平均成绩)
FROM user_profile -- 来自用户资料表
WHERE gender = 'male' -- 筛选条件:仅选择性别为男性的学生记录
;
26.计算25岁以上和以下的用户数量
# 方法一
## 根据年龄对用户进行分组,并计算每个年龄段的用户数量
SELECT
CASE
WHEN age < 25 OR age IS NULL THEN '25岁以下' -- 如果年龄小于25岁或为空,则归类为“25岁以下”
WHEN age >= 25 THEN '25岁及以上' -- 如果年龄大于等于25岁,则归类为“25岁及以上”
END AS age_cut, -- 年龄段分类结果列名为age_cut
COUNT(1) AS number -- 计算每个年龄段的用户数量
FROM
user_profile -- 数据来源于用户资料表
GROUP BY
1 -- 按照第一个选择列表达式(即CASE WHEN语句)进行分组
;
# 方法二
## 根据年龄对用户进行分组,并计算每个年龄段的用户数量
SELECT
IF(age >= 25, "25岁及以上", "25岁以下") AS age_cut, -- 使用IF函数将年龄分为两个年龄段
COUNT(1) AS number -- 计算每个年龄段的用户数量
FROM
user_profile -- 数据来源于用户资料表
GROUP BY
age_cut -- 按照年龄段(age_cut)进行分组
;
27.查看不同年龄段的用户明细
-- 问题分解:
-- 限定条件:无;
-- 划分年龄段:数值条件判断,可以用多重if,不过更方便的是用`case when [expr] then [result1]...else [default] end`
## 根据用户的年龄对用户进行分类,并返回设备ID、性别及年龄段
SELECT
device_id, -- 用户的设备ID
gender, -- 用户的性别
CASE
WHEN age < 20 THEN '20岁以下' -- 如果年龄小于20岁,则归类为“20岁以下”
WHEN age BETWEEN 20 AND 24 THEN '20-24岁' -- 如果年龄在20至24岁之间(包括20和24),则归类为“20-24岁”
WHEN age >= 25 THEN '25岁及以上' -- 如果年龄大于等于25岁,则归类为“25岁及以上”
WHEN age IS NULL THEN '其他' -- 如果年龄为空,则归类为“其他”
END AS age_cut -- 年龄段分类结果列名为age_cut
FROM
user_profile -- 数据来源于用户资料表
;
28.计算用户8月每天的练题数量
-- 问题分解:
-- 限定条件:2021年8月,写法有很多种,比如用year/month函数的`year(date)=2021 and month(date)=8`,比如用date_format函数的`date_format(date, "%Y-%m")="202108"`
-- 每天:按天分组`group by date`
-- 题目数量:count(question_id)
## 统计2021年8月每天的练习题目数量
SELECT
DAY(date) AS day, -- 提取日期中的“日”部分,并命名为day
COUNT(question_id) AS question_cnt -- 计算每个日期的题目数量
FROM
question_practice_detail -- 数据来源于练习详情表
WHERE
MONTH(date) = 8 AND YEAR(date) = 2021 -- 筛选条件:仅选择2021年8月的数据
GROUP BY
DAY(date) -- 按照日期中的“日”部分(即天)进行分组
;
29. 计算用户的平均次日留存率
-- 问题分解:
-- 限定条件:第二天再来。
-- 解法1:表里的数据可以看作是全部第一天来刷题了的,那么我们需要构造出第二天来了的字段,因此可以考虑用left join把第二天来了的拼起来,限定第二天来了的可以用`date_add(date1, interval 1 day)=date2`筛选,并用device_id限定是同一个用户。
-- 解法2:用lead函数将同一用户连续两天的记录拼接起来。先按用户分组`partition by device_id`,再按日期升序排序`order by date`,再两两拼接(最后一个默认和null拼接),即`lead(date) over (partition by device_id order by date)`
-- 平均概率:
-- 解法1:可以count(date1)得到左表全部的date记录数作为分母,count(date2)得到右表关联上了的date记录数作为分子,相除即可得到平均概率
-- 解法2:检查date2和date1的日期差是不是为1,是则为1(次日留存了),否则为0(次日未留存),取avg即可得平均概率。
# 方法一:
## 计算用户在连续两天进行练习的比例(平均留存率)
SELECT
COUNT(date2) / COUNT(date1) AS avg_ret -- 计算第二天有记录的用户数与第一天有记录的用户数之比
FROM (
SELECT
DISTINCT qpd.device_id, -- 用户设备ID
qpd.date AS date1, -- 第一天的日期
uniq_id_date.date AS date2 -- 第二天的日期
FROM question_practice_detail AS qpd
LEFT JOIN (
SELECT DISTINCT device_id, date -- 获取唯一设备ID和对应的日期
FROM question_practice_detail
) AS uniq_id_date
ON qpd.device_id = uniq_id_date.device_id -- 通过device_id进行连接
AND DATE_ADD(qpd.date, INTERVAL 1 DAY) = uniq_id_date.date -- 确保是连续两天
) AS id_last_next_date;
# 方法二:
## 计算用户在连续两天进行练习的比例(平均留存率)
SELECT
AVG(IF(DATEDIFF(date2, date1) = 1, 1, 0)) AS avg_ret -- 如果日期差为1,则计为1,否则计为0,最后求平均值
FROM (
SELECT
DISTINCT device_id,
date AS date1,
LEAD(date) OVER (PARTITION BY device_id ORDER BY date) AS date2 -- 使用LEAD函数获取每个用户的下一个日期
FROM (
SELECT DISTINCT device_id, date -- 获取唯一设备ID和对应的日期
FROM question_practice_detail
) AS uniq_id_date
) AS id_last_next_date;
# 方法三
## 使用了一个CTE(Common Table Expression,公用表表达式)来首先去除`question_practice_detail`表中的重复记录,然后计算用户在连续两天进行练习的比例
WITH tmp AS (
-- 去除重复的(device_id, date)组合
SELECT
device_id,
`date`
FROM question_practice_detail
GROUP BY 1, 2 -- 按device_id和date分组以去重
)
-- 计算用户在连续两天进行练习的比例(平均留存率)
SELECT
COUNT(b.`date`) / COUNT(a.`date`) AS avg_ret -- 第二天有记录的用户数与第一天有记录的用户数之比
FROM tmp a
LEFT JOIN tmp b ON a.device_id = b.device_id
AND a.`date` = DATE_SUB(b.`date`, INTERVAL 1 DAY) -- 确保是连续两天的数据
;
30.统计每种性别的人数
-- 问题分解:
-- 限定条件:无;
-- 每个性别:按性别分组group by gender,但是没有gender字段,需要从profile字段截取,按字符,分割后取出即可。可使用substring_index函数可以按特定字符串截取源字符串。
-- substring_index(FIELD, sep, n)可以将字段FIELD按照sep分隔:
-- (1).当n大于0时取第n个分隔符(n从1开始)左边的全部内容;
-- (2).当n小于0时取倒数第n个分隔符(n从-1开始)右边的全部内容;
-- 因此,本题可以直接用`substring_index(profile, ',', -1)`取出性别。
# 方法一
## 从profile字段中提取性别信息,并统计每个性别的用户数量
SELECT
SUBSTRING_INDEX(profile, ',', -1) AS gender, -- 使用SUBSTRING_INDEX函数提取profile字段中最后一个逗号后的部分作为性别
COUNT(device_id) AS user_count -- 统计每个性别的用户数量
FROM
user_submit -- 数据来源于user_submit表
GROUP BY
1 -- 按照第一个选择列表达式(即gender)进行分组
;
# 方法二
## 从profile字段中提取性别信息,并统计每个性别的用户数量
SELECT
(CASE
WHEN profile LIKE '%female%' THEN 'female' -- 如果profile包含'female',则性别为female
ELSE 'male' -- 否则,默认性别为male
END) AS gender, -- 将结果命名为gender
COUNT(1) AS user_count -- 统计每个性别的用户数量
FROM
user_submit -- 数据来源于user_submit表
GROUP BY
gender -- 按照gender列进行分组
;
31.提取博客URL中的用户名
## 从user_submit表中选择device_id和从blog_url中提取的用户名
SELECT
device_id, -- 用户设备ID
SUBSTRING_INDEX(blog_url, '/', -1) AS user_name -- 使用SUBSTRING_INDEX函数提取blog_url中最后一个斜杠后的部分作为用户名
FROM
user_submit; -- 数据来源于user_submit表
32.截取出年龄
## 从profile字段中提取年龄信息,并统计每个年龄段的用户数量
SELECT
SUBSTRING_INDEX(SUBSTRING_INDEX(profile, ',', -2), ',', 1) AS age, -- 提取profile字段中倒数第二个值作为年龄
COUNT(1) AS number -- 统计每个年龄段的用户数量
FROM
user_submit -- 数据来源于user_submit表
GROUP BY
age -- 按照提取出的年龄进行分组
;
33.找出每个学校GPA最低的同学
-- 问题分解:
-- 限定条件:gpa最低,看似min(gpa),但是要留意,是每个学校里的最低,不是全局最低。min(gpa)的时候对应同学的ID丢了,直接干是拿不到最低gpa对应的同学ID的;
-- 每个学校最低:
-- 第一种方式是用group by把学校分组,然后计算得到每个学校最低gpa,再去找这个学校里和这个gpa相等的同学ID。注意这样如果最低gpa对应多个同学,都会输出,题目没有明确此种情况,心理明白就行。
-- 第二种方式是利用窗口函数,先按学校分组计算排序gpa,得到最低gpa的记录在用子查询语法拿到需要的列即可。此题中rou_number可以得到排序后的位序,取位序为1即可得到最小值(升序时)。窗口函数语法:row_number/rank/dense_rank over (partition by FIELD1 order by FIELD2),
# 方法一
## 选择每个大学GPA最低的学生记录
SELECT
a.device_id, -- 用户设备ID
a.university, -- 用户所在大学
a.gpa -- 用户的GPA
FROM
user_profile a -- 主表别名为a
LEFT JOIN
(
-- 子查询b:获取每个大学的最低GPA
SELECT
university,
MIN(gpa) AS gpa
FROM
user_profile
GROUP BY
university -- 按大学分组以找到每个大学的最低GPA
) AS b ON a.university = b.university AND a.gpa = b.gpa -- 连接条件:大学相同且GPA相同
ORDER BY
a.university; -- 按大学排序结果
# 方法二
## 使用ROW_NUMBER()窗口函数选择每个大学GPA最低的学生记录
SELECT
device_id, -- 用户设备ID
university, -- 用户所在大学
gpa -- 用户的GPA
FROM
(
-- 子查询univ_min:为每个大学的学生根据GPA进行排序
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY university ORDER BY gpa) AS rn -- 使用ROW_NUMBER()为每个大学的学生按GPA排序
FROM
user_profile -- 数据来源于user_profile表
) AS univ_min
WHERE
rn = 1 -- 只选择每个大学中GPA最低(即rn=1)的学生记录
ORDER BY
university; -- 按大学排序结果
# 方法三
## 选择每个大学GPA最低的学生记录
SELECT
device_id, -- 用户设备ID
university, -- 用户所在大学
gpa -- 用户的GPA
FROM
user_profile up1 -- 主查询中的别名为up1
WHERE
(university, gpa) IN (
SELECT
university,
MIN(gpa) AS min_gpa
FROM
user_profile up2 -- 子查询中的别名为up2
GROUP BY
university -- 按大学分组以找到每个大学的最低GPA
)
ORDER BY
university; -- 按大学排序结果
34.统计复旦用户8月练题情况
-- 问题分解:
-- 限定条件:需要是复旦大学的(来自表user_profile.university),8月份练习情况(来自表question_practice_detail.date)
-- 从date中取month:用month函数即可;
-- 总题目:count(question_id)
-- 正确的题目数:`sum(if(qpd.result='right', 1, 0))`
-- 按列聚合:需要输出每个用户的统计结果,因此加上`group by up.device_id`
# 方法一
## 复旦大学的每个用户在8月份练习的总题目数和回答正确的题目数情况
select up.device_id, '复旦大学' as university,
count(question_id) as question_cnt,
sum(if(qpd.result='right', 1, 0)) as right_question_cnt
from user_profile as up
left join question_practice_detail as qpd
on qpd.device_id = up.device_id and month(qpd.date) = 8
where up.university = '复旦大学'
group by up.device_id
# 方法二
## 复旦大学的每个用户在8月份练习的总题目数和回答正确的题目数情况
-- 第一部分:统计2021年8月期间复旦大学学生的练习情况
SELECT
a.device_id AS device_id,
b.university AS university,
COUNT(1) AS question_cnt, -- 统计每个学生在2021年8月的总练习题目数
SUM(IF(result = 'right', 1, 0)) AS right_question_cnt -- 统计每个学生在2021年8月的正确回答题目数
FROM
question_practice_detail a
LEFT JOIN
user_profile b ON a.device_id = b.device_id
WHERE
LEFT(a.date, 7) = '2021-08' -- 筛选出2021年8月的数据
AND b.university = '复旦大学' -- 仅考虑复旦大学的学生
AND b.device_id IS NOT NULL -- 确保user_profile中有对应记录
GROUP BY
1, 2
UNION ALL
-- 第二部分:找出2021年8月没有进行任何练习的复旦大学学生
SELECT
a.device_id AS device_id,
a.university AS university,
0 AS question_cnt, -- 没有练习题目,所以题目数量为0
0 AS right_question_cnt -- 没有练习题目,所以正确回答题目数量也为0
FROM
user_profile a
LEFT JOIN
question_practice_detail b ON a.device_id = b.device_id
AND LEFT(b.date, 7) = '2021-08' -- 尝试匹配2021年8月的数据
WHERE
b.device_id IS NULL -- 只选择那些在2021年8月没有任何练习记录的学生
AND a.university = '复旦大学'; -- 仅考虑复旦大学的学生
35.浙大不同难度题目的正确率
-- 问题分解:
-- 限定条件:浙江大学的用户;
-- 不同难度:difficult_level(question_detail表中的列),需要分组统计,因此用到group by,
-- 正确率:表面理解就是正确数÷总数,正确的是result='right'(question_practice_detail表),数目用函数count,总数是count(question_id);
-- 多张表联合查询:需要用到join,join有多种语法,因为条件限定需要是浙江大学的用户,所以需要是user_profile表的并且能统计出题目难度的记录,因此用user_profile表inner join另外两张表。
# 方法一
## 计算浙江大学学生在不同难度级别的题目上的平均正确率
SELECT
qd.difficult_level, -- 难度级别
AVG(IF(qpd.result = 'right', 1, 0)) AS correct_rate -- 正确率:使用AVG函数计算每个难度级别下正确回答的比例
FROM
user_profile AS up -- 用户信息表
INNER JOIN
question_practice_detail AS qpd ON up.device_id = qpd.device_id -- 连接条件:设备ID相同
INNER JOIN
question_detail AS qd ON qd.question_id = qpd.question_id -- 连接条件:题目ID相同
WHERE
up.university = '浙江大学' -- 仅考虑浙江大学的学生
GROUP BY
qd.difficult_level -- 按难度级别分组
ORDER BY
correct_rate ASC; -- 按正确率升序排序
# 方法二
SELECT
c.difficult_level AS difficult_level, -- 难度级别
SUM(IF(a.result = 'right', 1, 0)) / COUNT(1) AS correct_rate -- 正确率:正确回答的题目数除以总题目数
FROM
question_practice_detail a
LEFT JOIN
user_profile b ON a.device_id = b.device_id AND b.university = '浙江大学' -- 连接条件:设备ID相同且大学为浙江大学
LEFT JOIN
question_detail c ON a.question_id = c.question_id -- 连接条件:题目ID相同
WHERE
b.device_id IS NOT NULL -- 确保user_profile中有对应记录
GROUP BY
1 -- 按难度级别分组
ORDER BY
2; -- 按正确率排序
36. 查找后排序
## 取出用户信息表中的用户设备 ID 和用户年龄,并按照年龄升序排序
SELECT
device_id, -- 用户设备ID
age -- 用户年龄
FROM
user_profile -- 数据来源于user_profile表
ORDER BY
2; -- 按照第二列(即age)进行排序
37.查找后多列排序
## 取出用户信息表中的device_id、年龄和gpa数据,并先按照gpa升序排序,再按照年龄升序排序
SELECT
device_id, -- 用户设备ID
gpa, -- 用户的GPA
age -- 用户的年龄
FROM
user_profile -- 数据来源于user_profile表
ORDER BY
2, -- 按照第二列(即gpa)进行排序
3; -- 在gpa相同的情况下,按照第三列(即age)进行排序
38.查找后降序排列
## 取出用户信息表中对应的数据,并先按照gpa降序排列、gpa相同的按照年龄降序排序
SELECT
device_id, -- 用户设备ID
gpa, -- 用户的GPA
age -- 用户的年龄
FROM
user_profile -- 数据来源于user_profile表
ORDER BY
2 DESC, -- 按照第二列(即gpa)进行降序排序
3 DESC; -- 在gpa相同的情况下,按照第三列(即age)进行降序排序
39.21年8月份练题总数
-- 问题分解:
-- 限定条件:2021年8月份,匹配date字段即可,匹配方法主要有三种:
-- (1)like语法:`date like "2021-08%"`
-- (2)year、month函数:`year(date)='2021' and month(date)='08'`;
-- (3)date_format函数:`date_format(date, '%Y-%m')='2021-08';`
-- 总用户数:count函数计数,因为用户有重复,所以需要distinct去重,即`count(distinct device_id)`
-- 总次数:count(question_id)即可
## 2021年8月份所有练习过题目的总用户数和练习过题目的总次数
SELECT
COUNT(DISTINCT device_id) AS did_cnt, -- 统计不同device_id的数量
COUNT(question_id) AS question_cnt -- 统计所有question_id的数量,即题目练习次数
FROM
question_practice_detail -- 数据来源于question_practice_detail表
WHERE
`date` LIKE '2021-08%'; -- 筛选出日期为2021年8月的记录