Bootstrap

【牛客网】非技术快速入门题库 SQL 篇

【牛客网】非技术快速入门题库 SQL 篇

牛客网在线编程_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月的记录
;