0
点赞
收藏
分享

微信扫一扫

HiveSQL一本通 - 案例实操

莞尔小迷糊 03-22 12:30 阅读 6
hive

文章目录


0.HiveSQL一本通使用说明

HiveSQL一本通旨在帮助大家熟悉HiveSQL的各种使用中奇技yin巧。
(1)如果你是刚学SQL,使用【基础/初级】案例练习即可。
(2)如果你是为了面试突击可以直接看【中/高级】案例练习。
面试中的SQL一般是各种窗口函数的妙用,可以先复习一下以下内容
Hive窗口函数的介绍
Hive窗口函数的使用案例
(3)如果你为了成为HiveSQL的专家那就全部看吧,好好敲一遍。

6.综合案例练习之基础查询

通过前面几章内容的学习,相信读者对Hive的具体使用已经有了一定的了解。本章将基于第3、4、5章讲解的Hive SQL语法,给出不同角度的综合案例练习题。首先是环境准备部分,读者可以跟随内容创建数据表并载入数据,供后续练习使用。接下来的练习题部分,读者可以先自行尝试解答,再对比给出的解答思路。需要读者注意的是,每道练习题的解答思路都不是唯一的,书中仅给出了其中一种解答思路,读者可以自行思考多种解决方案,并评估不同方案的性能,这也是后续内容将会重点讲解的。

6.1 环境准备

本章所有的练习题都将基于同一组数据表,本节需要完成数据准备工作

创建数据表

hive>
– 创建学生表
DROP TABLE IF EXISTS student_info;
create table if not exists student_info(
stu_id string COMMENT ‘学生id’,
stu_name string COMMENT ‘学生姓名’,
birthday string COMMENT ‘出生日期’,
sex string COMMENT ‘性别’
)
row format delimited fields terminated by ‘,’
stored as textfile;

– 创建课程表
DROP TABLE IF EXISTS course_info;
create table if not exists course_info(
course_id string COMMENT ‘课程id’,
course_name string COMMENT ‘课程名’,
tea_id string COMMENT ‘任课老师id’
)
row format delimited fields terminated by ‘,’
stored as textfile;

– 创建老师表
DROP TABLE IF EXISTS teacher_info;
create table if not exists teacher_info(
tea_id string COMMENT ‘老师id’,
tea_name string COMMENT ‘老师姓名’
)
row format delimited fields terminated by ‘,’
stored as textfile;

– 创建分数表
DROP TABLE IF EXISTS score_info;
create table if not exists score_info(
stu_id string COMMENT ‘学生id’,
course_id string COMMENT ‘课程id’,
score int COMMENT ‘成绩’
)
row format delimited fields terminated by ‘,’
stored as textfile;

数据准备

(1)创建/opt/module/data目录
[atguigu@hadoop102 module]$ mkdir data
(2)将student_info.txt、course_info.txt、teacher_info.txt和score_info.txt 4个文件上传至/opt/module/data目录下。以上4个数据文件在本书附赠的资料中可以找到。
(3)4个文件中的数据内容分别如下所示,篇幅所限,仅展示部分
数据。

[atguigu@hadoop102 data]$ vim student_info.txt

001,陈富贵,1995-05-16,男
002,李建国,1994-03-20,男
003,杨建军,1995-04-30,男
004,刘爱党,1998-08-28,男
[atguigu@hadoop102 data]$ vim course_info.txt

01,语文,1003
02,数学,1001
03,英语,1004
04,体育,1002
05,音乐,1002

[atguigu@hadoop102 data]$ vim teacher_info.txt

1001,张高数
1002,李体音
1003,王子文
1004,刘丽英

[atguigu@hadoop102 data]$ vim score_info.txt

001,01,94
002,01,74
004,01,85
005,01,64

加载数据

(1)将数据文件分别加载至表中。
hive>
load data local inpath ‘/opt/module/data/student_info.txt’ into table student_info;

load data local inpath ‘/opt/module/data/course_info.txt’ into table course_info;

load data local inpath ‘/opt/module/data/teacher_info.txt’ into table teacher_info;

load data local inpath ‘/opt/module/data/score_info.txt’ into table score_info;
(2)验证插入数据情况
hive>
select * from student_info limit 5;
select * from course_info limit 5;
select * from teacher_info limit 5;
select * from score_info limit 5;

6.2 简单查询练习

1.查询姓名中带“山”的学生名单

(1)思路分析。
本题主要考察对where子句与关系运算符like的结合使用。
(2)查询语句。
hive>
select
*
from student_info
where stu_name like “%山%”;
(3)查询结果。
stu_id stu_name birthday sex
006 廖景山 1992-11-12 男
010 吴山 1998-08-23 男

2.查询姓“王”老师的个数

(1)思路分析。
本题主要考察对where子句与关系运算符like的结合使用,然后再结合count()聚合函数完成对符合结果的数据个数的统计。
(2)查询语句。
hive>
select
count(*) wang_count
from teacher_info
where tea_name like ‘王%’;
(3)查询结果。
wang_count
1

3.检索课程编号为“04”且分数小于60的学生的分数信息,结果按分数降序排列

(1)思路分析。
本题主要通过where子句、关系运算符和逻辑运算符的综合使用得到课程编号为“04”且分数小于60的学生的课程信息,最后使用order by关键字进行排序操作。
(2)查询语句。
hive>
select
stu_id,
course_id,
score
from score_info
where course_id =‘04’ and score<60
order by score desc;
(3)查询结果。
stu_id course_id score
004 04 59
001 04 54
020 04 50
014 04 40
017 04 34
010 04 34

4.查询数学成绩不及格的学生信息和其对应的数学学科成绩,按照学号升序排序

(1)思路分析。
本案例分两步完成。
第一步:根据学科名称为“数学”作为条件查询,找到数学学科对应的学科编号,再根据学科编号和分数小于60的条件,在score_info表中获取到相关的成绩信息。
第二步:第一步已经获取到相关成绩信息,但是要求获取对应的学生信息,所以要用第一步中获取的成绩信息和学生表student_info进行join关联查询,关联字段为学生编号stu_id。最终获取到学生以及相关成绩信息,再使用order by关键字进行排序即可。
(2)查询语句。
hive>
select
s.stu_id,
s.stu_name,
t1.score
from student_info s
join (
select
*
from score_info
where course_id=(select course_id from course_info where course_name=‘数学’) and score < 60
) t1 on s.stu_id = t1.stu_id
order by s.stu_id;
(3)查询结果。
s.stu_id s.stu_name t1.score
005 韩华翰 44
007 孟海 55
008 宋忠 34
011 邱钢 49
013 许晗晗 35
014 谢思萌 39
015 乔白凝 48
017 熊巧 34
018 黄瑗 58
019 乔颜 39
020 于丝 59

6.3 分组与汇总练习

6.3.1 汇总练习

1.查询编号为“02”的课程的总成绩

(1)思路分析。

本题主要考查分组聚合知识点。
首先将score_info表中课程编号为“02”的数据过滤出来,接下来为了统计该课程的总成绩,可以利用course_id字段分组,再结合sum()聚合函数累加求和,最终获取成绩的总和。
(2)查询语句。
hive>
select
course_id,
sum(score) score_sum
from score_info
where course_id=‘02’
group by course_id;
(3)查询结果。
course_id score_sum
02 1133

  1. 查询参加考试的学生个数
    (1)思路分析。
    本题主要考查distinct去重,结合count聚合函数统计学生个数。
    (2)查询语句。
    hive>
    select
    count(distinct stu_id) stu_num
    from score_info;
    (3)查询结果。
    stu_num
    19

6.3.2 分组练习

1.查询各科成绩最高和最低的分,以如下的形式显示:课程号、最高分、最低分

(1)思路分析。
本题主要考查了使用分组聚合求最大值和最小值。
按照course_id字段进行分组,通过max和min函数获取各科成绩的最大值和最小值。
(2)查询语句。
hive>
select
course_id,
max(score) max_score,
min(score) min_score
from score_info
group by course_id;
(3)查询结果。
course_id max_score min_score
01 94 38
02 93 34
03 99 32
04 100 34
05 87 59

2.查询每门课程有多少学生参加了考试(有考试成绩)

(1)思路分析。
本题主要考查分组聚合的使用。
根据题意,首先按照course_id字段进行分组,再通过count函数对stu_id进行个数统计,即可获取每一门课程的参加考试的学生人数。
(2)查询语句。
hive>
select
course_id,
count(stu_id) stu_num
from score_info
group by course_id;
(3)查询结果。
course_id stu_num
01 19
02 19
03 19
04 12
05 5

3.查询男生、女生人数

(1)思路分析。
本题主要考查的是分组聚合的使用。按照sex字段分组,并使用count聚合函数统计人数即可。
(2)查询语句。
hive>
select
sex,
count(stu_id) count
from student_info
group by sex;
(3)查询结果。
sex count
女 9
男 11

6.3.3 对分组结果的条件查询

1.查询平均成绩大于60分的学生的学号和平均成绩

(1)思路分析。
首先使用分组聚合得到每个学生的平均成绩。然后对平均成绩按照指定条件过滤,得到平均成绩高于60分的学生和成绩。平均成绩是聚合得到的结果,对聚合结果的过滤需要使用having关键字,而不是where子句。
(2)查询语句。
hive>
select
stu_id,
avg(score) score_avg
from score_info
group by stu_id
having score_avg > 60;
(3)查询结果。
stu_id score_avg
001 72.5
002 86.25
004 81.5
005 75.4
006 73.33333333333333
009 74.2
013 61.0
015 70.25
016 81.25
020 69.75

2.查询至少考了四门课程的学生学号

(1)思路分析。
首先使用分组聚合,按照stu_id进行分组,使用count聚合函数统计course_id个数,得到每个学生考试课程的个数。最后使用having关键字对聚合后得到的课程个数进行过滤,得到选修课程数大于等于4的学生。
(2)查询语句。
hive>
select
stu_id,
count(course_id) course_count
from score_info
group by stu_id
having course_count >=4;
(3)查询结果。
stu_id course_num
001 4
002 4
004 4
005 5
007 5
009 5
010 4
013 4
014 4
015 4
016 4
017 4
018 4
020 4

3.查询每门课程的平均成绩,结果按平均成绩升序排序,平均成绩相同时,按课程号降序排列

(1)思路分析。
本题主要思路是使用分组聚合计算每门课程的平均成绩,然后使用order by关键字按照题目要求对结果进行排序。使用order by关键字时,配合asc是升序,配合desc是降序,默认为升序。
(2)查询语句。
hive>
select
course_id,
avg(score) score_avg
from score_info
group by course_id
order by score_avg asc, course_id desc;
(3)查询结果。
course_id score_avg
02 59.63157894736842
04 63.416666666666664
01 67.15789473684211
03 69.42105263157895
05 74.6

4.统计参加考试人数大于等于15的学科

(1)思路分析。
使用分组聚合得到每门学科参加考试的人数,使用having关键字过滤人数大于等于15的学科。
(2)查询语句。
hive>
select
course_id,
count(stu_id) stu_count
from score_info
group by course_id
having stu_count >= 15;
(3)查询结果。
course_id stu_count
01 19
02 19
03 19

6.3.4 查询结果排序和分组指定条件

1.查询学生的总成绩并按照总成绩降序排序

(1)思路分析。
本题主要考查分组聚合和order by关键字的使用。
(2)查询语句。
hive>
select
stu_id,
sum(score) sum_score
from score_info
group by stu_id
order by sum_score desc;
(3)查询结果。
stu_id sum_score
005 377
009 371
002 345
004 326
016 325
007 299
001 290
015 281
020 279
013 244
010 233
018 232
006 220
014 192
017 181
012 180
011 180
019 178
008 129

2.查询一共参加三门课程且其中一门为语文课程的学生的id和姓名

(1)思路分析。
本题主要考查分组group by、使用having关键字进行条件过滤,以及多表关联的综合使用。
第一步: 在course_info表中通过course_name为“语文”查询得到语文课程的course_id。
第二步:在score_info表中查询所有学习语文课程的学生的stu_id。
第三步:利用in关键字结合第二步的查询结果,查询成绩表score_info,得到参加语文课程的学生的所有课程course_id。
第四步:对第三步的查询结果按照stu_id分组,使用count聚合函数得到学生参加的课程数,结合having关键字,筛选聚合结果为3的stu_id。
第五步:将第四步的查询结果与student_info表关联,得到学生姓名stu_name字段。
(2)查询语句。
hive>
select
si.stu_id,
si.stu_name
from student_info si
join
(
select stu_id,
count() cc
from score_info
where stu_id in (select stu_id
from score_info
where course_id = (select course_id from course_info where course_name = ‘语文’))
group by stu_id
having count(
) = 3
) t1
on
si.stu_id=t1.stu_id
(3)查询结果。
stu_id stu_name
006 廖景山
008 宋忠
011 邱钢
012 邓夏波
019 乔颜

6.4 复杂查询练习

1.查询没有学全所有课的学生的学号、姓名

(1)思路分析。
对题目进行分析,没有学全所有课程,也就是说该学生选修的课程数量小于总的课程数量。
本题主要考查分组聚合与多表关联的综合使用。
第一步:将学生表作为主表和成绩表进行left join,并且按照学生学号和姓名分组 对每一个学生所学的课程进行count统计。
第二步:直接通过having的方式进行对没有学全的学生进行过滤。
(2)查询语句。
hive>
select sti.stu_id,
sti.stu_name,
count(sci.course_id)
from student_info sti
left join
score_info sci
on
sti.stu_id = sci.stu_id
group by sti.stu_id, sti.stu_name
having count(sci.course_id)<(select count(*) from course_info)
(3)查询结果。
t2.stu_id t2.stu_name t2.sc_count
001 陈富贵 4
002 李建国 4
003 杨建军 0
004 刘爱党 4
006 廖景山 3
008 宋忠 3
010 吴山 4
011 邱钢 3
012 邓夏波 3
013 许晗晗 4
014 谢思萌 4
015 乔白凝 4
016 钟紫 4
017 熊巧 4
018 黄瑗 4
019 乔颜 3
020 于丝 4

2.查询出只选修了三门课程的全部学生的学号和姓名

(1)思路分析。
本题主要考查分组后的聚合结果过滤,以及关联查询的使用。
第一步:查询成绩表,按照学生学号分组,然后使用count聚合函数对课程数进行统计,然后利用having关键字对数据进行过滤,得到选修课程数为3的结果。将查询结果作为临时表t1。
第二步:对学生表和临时表t1进行关联查询,关联字段为学生学号,获取学生姓名信息。
(2)查询语句。
hive>
select
s.stu_id,
s.stu_name
from student_info s
join (
select
stu_id,
count(course_id) course_count
from score_info
group by stu_id
having course_count =3
) t1
on s.stu_id = t1.stu_id;
(3)查询结果。
s.stu_id s.stu_name
006 廖景山
008 宋忠
011 邱钢
012 邓夏波
019 乔颜

6.5 多表查询练习

6.5.1 表连接

1.查询所有学生的学号、姓名、选课数、总成绩

(1)思路分析。
本题主要考查多表的关联查询和分组聚合的使用。
以学生表作为主表 使用left join和成绩表进行关联查询,stu_id作为关联字段,再按照stu_id和stu_name作为分组字段,最终对课程数和总成绩进行统计。
(2)查询语句。
hive>
select
s.stu_id,
s.stu_name,
count(sc.course_id) count_course,
nvl(sum(sc.score),0) sum_score
from student_info s
left join score_info sc on s.stu_id = sc.stu_id
group by s.stu_id,s.stu_name;
(3)查询结果。
stu_id stu_name course_count course_sum
001 陈富贵 4 290
002 李建国 4 345
003 杨建军 0 0
004 刘爱党 4 326
005 韩华翰 5 377
006 廖景山 3 220
007 孟海 5 299
008 宋忠 3 129
009 韩福 5 371
010 吴山 4 233
011 邱钢 3 180
012 邓夏波 3 180
013 许晗晗 4 244
014 谢思萌 4 192
015 乔白凝 4 281
016 钟紫 4 325
017 熊巧 4 181
018 黄瑗 4 232
019 乔颜 3 178
020 于丝 4 279

2.查询平均成绩大于85的所有学生的学号、姓名和平均成绩

(1)思路分析。
本题依然主要考查多表的关联查询和分组聚合的使用。
以成绩表关联学生表,根据学生学号和学生名称作为分组字段,通过avg()函数获取组内平均成绩,最后使用having关键字过滤聚合结果,得到平均成绩大于85的数据。
(2)查询语句。
hive>
select s.stu_id,
s.stu_name,
avg(sc.score) avg_score
from score_info sc
join student_info s on s.stu_id = sc.stu_id
group by s.stu_id, s.stu_name
having avg_score > 85
(3)查询结果。
stu_id stu_name avg_score
002 李建国 86.25

3.查询学生的选课情况:学号,姓名,课程号,课程名称

(1)思路分析。
本题主要考查多表关联。
根据题目中的信息,需要分别关注成绩表、学生表、课程表,本题关键就是定位表与表之间的关联字段。course_info表和score_info表通过course_id字段进行关联,student_info表和score_info表通过stu_id字段进行关联。
(2)查询语句。
hive>
select
sti.stu_id,
sti.stu_name,
ci.course_id,
ci.course_name
from
student_info sti
left join
score_info sci
on
sti.stu_id=sci.stu_id
left join
course_info ci
on
sci.course_id=ci.course_id
(3)查询结果。
sti.stu_id sti.stu_name ci.course_id ci.course_name
001 陈富贵 01 语文
001 陈富贵 02 数学
001 陈富贵 03 英语
001 陈富贵 04 体育
002 李建国 01 语文
002 李建国 02 数学
002 李建国 03 英语
002 李建国 04 体育
003 杨建军 NULL NULL
004 刘爱党 01 语文
004 刘爱党 02 数学
004 刘爱党 03 英语
004 刘爱党 04 体育

答案一共75行 只做部分展示

4.查询课程编号为03且课程成绩在80分以上的学生的学号和姓名及课程信息

(1)思路分析。
本题主要考查多表关联和条件过滤的综合使用。
第一步:查询成绩表,过滤课程编号为03且课程成绩在80分以上的信息,结果作为临时表t1。
第二步:查询学生表并join临时表t1,关联字段为stu_id,同时join课程表,关联字段为course_id,获取最终结果。
(2)查询语句。
hive>
select
s.stu_id,
s.stu_name,
t1.score,
t1.course_id,
c.course_name
from student_info s
join (
select
stu_id,
score,
course_id
from score_info
where score > 80 and course_id = ‘03’
) t1
on s.stu_id = t1.stu_id
join course_info c on c.course_id = t1.course_id;
(3)查询结果。
s.stu_id s.stu_name t1.score t1.course_id c.course_name
002 李建国 87 03 英语
004 刘爱党 89 03 英语
005 韩华翰 99 03 英语
013 许晗晗 93 03 英语
015 乔白凝 84 03 英语
019 乔颜 93 03 英语
020 于丝 81 03 英语
Time taken: 9.064 seconds, Fetched: 7 row(s)

6.5.2 多表连接

1.课程编号为"01"且课程分数小于60,按分数降序排列的学生信息

(1)思路分析。
第一步:查询成绩表,过滤出课程编号为“01”且课程分数小于60的信息,结果作为临时表t1。
第二步:查询学生表join关联临时表t1,关联字段为stu_id,获取相关学生信息,最后对成绩字段进行倒序排列。
(2)查询语句。
hive>
select
s.stu_id,
s.stu_name,
s.birthday,
s.sex,
t1.score
from student_info s
join (
select
stu_id,
course_id,
score
from score_info
where score < 60 and course_id = ‘01’
) t1
on s.stu_id=t1.stu_id
order by t1.score desc;
(3)查询结果。
s.stu_id s.stu_name s.birthday s.sex t1.score
017 熊巧 1992-07-04 女 58
008 宋忠 1994-02-06 男 56
007 孟海 1999-04-09 男 48
013 许晗晗 1997-11-08 女 47
019 乔颜 1994-08-31 女 46
012 邓夏波 1996-12-21 女 44
018 黄瑗 1993-09-24 女 38
Time taken: 8.936 seconds, Fetched: 7 row(s)

2.查询所有课程成绩在70分以上的学生的姓名、课程名称和分数,按分数升序排列

(1)思路分析。
按照题目要求,本题的关键在于如何查询得到所有课程成绩在70分以上的的学生信息。
第一步:首先需要按照学生学号分组,将sum聚合函数与if逻辑判断函数巧妙联合使用,课程成绩在70分以上计为0,否则为1,再将其sum求和作为flage字段,使用having关键字过滤flage,flage为0的即为所有课程成绩在70分以上的学生学号,作为临时表t1。
第二步:查询学生表,通过join和临时表t1关联,关联字段为stu_id,获取学生姓名息。
第三步:与成绩表和课程表做join关联查询,获得课程名和成绩信息。
(2)查询语句。
hive>
select
s.stu_id,
s.stu_name,
c.course_name,
s2.score
from student_info s
join (
select
stu_id,
sum(if(score >= 70,0,1)) flage
from score_info
group by stu_id
having flage =0
) t1
on s.stu_id = t1.stu_id
join score_info s2 on s.stu_id = s2.stu_id
join course_info c on s2.course_id = c.course_id
order by S2.score
(3)查询结果。
s.stu_id s.stu_name c.course_name s2.course
016 钟紫 语文 71
016 钟紫 英语 71
002 李建国 语文 74
002 李建国 数学 84
002 李建国 英语 87
016 钟紫 数学 89
016 钟紫 体育 94
002 李建国 体育 100
Time taken: 27.166 seconds, Fetched: 8 row(s)

3.查询该学生不同课程的成绩相同的学生编号、课程编号、学生成绩

(1)思路分析。
本题主要考查表关联时关联条件的灵活应用。
此题主要针对成绩表进行查询,关键使用成绩表和自身进行关联来获取数据,关联条件就是学生编号相同、课程编号不同,以及成绩相同。简单来说就是查询一个学生哪些不同课程考了相同的成绩。
(2)查询语句。
hive>
select
sc1.stu_id,
sc1.course_id,
sc1.score
from score_info sc1
join score_info sc2 on sc1.stu_id = sc2.stu_id
and sc1.course_id <> sc2.course_id
and sc1.score = sc2.score;
(3)查询结果。
sc1.stu_id sc1.course_id sc1.score
016 03 71
017 04 34
016 01 71
005 05 85
007 05 63
009 05 79
017 02 34
005 04 85
007 04 63
009 04 79
Time taken: 8.881 seconds, Fetched: 10 row(s)

4.查询课程编号为“01”的课程比“02”的课程成绩高的所有学生的学号

(1)思路分析。
本题主要考查嵌套查询结合join关联查询获取信息。
第一步:首先查询成绩表,将课程编号为01和课程编号为02的信息获取到,结果分别为临时表s1和s2。
第二步:将临时表s1和临时表s2进行join关联,关联字段为学生编号stu_id,并结合s1中的成绩大于s2的成绩为条件进行过滤获取结果。
(2)查询语句。
hive>
select
s1.stu_id
from
(
select
sc1.stu_id,
sc1.course_id,
sc1.score
from score_info sc1
where sc1.course_id =‘01’
) s1
join
(
select
sc2.stu_id,
sc2.course_id,
score
from score_info sc2
where sc2.course_id =“02”
)s2
on s1.stu_id=s2.stu_id
where s1.score > s2.score;
(3)查询结果。
stu_id
001
005
008
010
011
013
014
015
017
019
020

5.查询学过编号为“01”的课程并且也学过编号为“02”的课程的学生的学号、姓名

(1)思路分析。
本题主要考查嵌套查询结合join关联获取信息。
第一步:查询成绩表,获取出课程编号为01的学生编号,再次查询成绩表将课程编号为01的学生编号作为联合条件过滤出同时也选修了课程编号为02的课程的学生编号,查询结果为临时表t1。
第二步:将临时表t1和学生表student_info进行join关联学生姓名。
(2)查询语句。
hive>
select
t1.stu_id as 学号,
s.stu_name as 姓名
from
(
select
stu_id
from score_info sc1
where sc1.course_id=‘01’
and stu_id in (
select
stu_id
from score_info sc2
where sc2.course_id=‘02’
)
)t1
join student_info s
on t1.stu_id = s.stu_id;
(3)查询结果。
学号 姓名
001 陈富贵
002 李建国
004 刘爱党
005 韩华翰
006 廖景山
007 孟海
008 宋忠
009 韩福
010 吴山
011 邱钢
012 邓夏波
013 许晗晗
014 谢思萌
015 乔白凝
016 钟紫
017 熊巧
018 黄瑗
019 乔颜
020 于丝
Time taken: 10.161 seconds, Fetched: 19 row(s)

6.查询学过“李体音”老师所教的所有课的同学的学号、姓名

(1)思路分析。
第一步:根据题目要求,通过将课程表和老师表关联获取“李体音”老师所教课程的课程编号。
第二步:将第一步中获取的课程编号作为条件,查询成绩表获取相关信息,并根据学生编号分组,通过having关键字组内进行过滤,过滤条件很关键,要想获得学过“李体音”老师所教的所有课的同学,必须满足count统计组内条数等于李体音的课程总数的条件。将查询结果作为临时表t1。
第三步:将临时表t1和学生表进行关联查询,获取最终的结果。
(2)查询语句。
hive>
select t1.stu_id,
si.stu_name
from (
select stu_id
from score_info si
where course_id in
(
select course_id
from course_info c
join teacher_info t
on c.tea_id = t.tea_id
where tea_name = ‘李体音’
)
group by stu_id
having count() = (select count()
from course_info c
join teacher_info t
on c.tea_id = t.tea_id
where tea_name = ‘李体音’)
) t1
join student_info si
on t1.stu_id = si.stu_id;
(3)查询结果。
s.stu_id s.stu_name
005 韩华翰
007 孟海
009 韩福
Time taken: 27.16 seconds, Fetched: 3 row(s)

7.查询学过“李体音”老师所讲授的任意一门课程的学生的学号、姓名

(1)思路分析。
本题和上一题的要求相近,区别在于是学过“李体音”老师的所有课还是任意一门。所以在第二步中,不再对查询结果做有条件过滤。
第一步:根据题目要求,通过将课程表和老师表关联获取“李体音”老师所教课程的课程编号。
第二步:将第一步中获取的课程编号作为条件,查询成绩表获取相关信息,并根据学生编号分组,将查询结果作为临时表t1。
第三步:将临时表t1和学生表进行关联查询,获取最终的结果。
(2)查询语句。
hive>
select
t1.stu_id,
si.stu_name
from
(
select
stu_id
from score_info si
where course_id in
(
select
course_id
from course_info c
join teacher_info t
on c.tea_id = t.tea_id
where tea_name=‘李体音’
)
group by stu_id
)t1
join student_info si
on t1.stu_id=si.stu_id;
(3)查询结果。
s.stu_id s.stu_name
001 陈富贵
002 李建国
004 刘爱党
005 韩华翰
007 孟海
009 韩福
010 吴山
013 许晗晗
014 谢思萌
015 乔白凝
016 钟紫
017 熊巧
018 黄瑗
020 于丝
Time taken: 9.391 seconds, Fetched: 14 row(s)

8.查询没学过"李体音"老师讲授的任一门课程的学生姓名

(1)思路分析。
本题考查的是上一题的查询结果的反向查询结果,关键在于not in关键字的使用。
第一步:根据题目要求,通过将课程表和老师表关联获取“李体音”老师所教课程的课程编号。
第二步:将第一步中获取的课程编号作为条件,查询成绩表做in包含查询,获取“李体音”老师所教的学生编号,并根据学生编号分组。
第三步:查询学生信息表,根据第二步查询的结果做not in过滤,获取结果。
(2)查询语句。
hive>
select
stu_id,
stu_name
from student_info
where stu_id not in
(
select
stu_id
from score_info si
where course_id in
(
select
course_id
from course_info c
join teacher_info t
on c.tea_id = t.tea_id
where tea_name=‘李体音’
)
group by stu_id
);
(3)查询结果。
stu_id stu_name
003 杨建军
006 廖景山
008 宋忠
011 邱钢
012 邓夏波
019 乔颜
Time taken: 36.559 seconds, Fetched: 6 row(s)

9.查询至少有一门课与学号为“001”的学生所学课程相同的学生的学号和姓名

(1)思路分析。
本题主要考查多条件数据过滤,关键是分析需求利用逆向思维解题。
第一步:查询成绩表中学生编号为001的课程编号。
第二步:将成绩表和学生表进行关联查询,关联字段为学生编号,并按照学生编号和姓名分组。根据第一步获取的课程编号作为条件做in包含查询。这里还有关键一步就是查询条件中要对001自身进行排除。
(2)查询语句。
hive>
select
si.stu_id,
si.stu_name
from score_info sc
join student_info si
on sc.stu_id = si.stu_id
where sc.course_id in
(
select
course_id
from score_info
where stu_id=‘001’ --001的课程
) and sc.stu_id <> ‘001’ --排除001学生
group by si.stu_id,si.stu_name;
(3)查询结果。
s1.stu_id s2.stu_name
002 李建国
004 刘爱党
005 韩华翰
006 廖景山
007 孟海
008 宋忠
009 韩福
010 吴山
011 邱钢
012 邓夏波
013 许晗晗
014 谢思萌
015 乔白凝
016 钟紫
017 熊巧
018 黄瑗
019 乔颜
020 于丝
Time taken: 8.97 seconds, Fetched: 18 row(s)

10.按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩

(1)思路分析。
本题主要考查多表联查结合分组统计查询。
第一步:查询成绩表,按照学生编号分组,获取每一个学生的平均成绩。作为临时结果表t1。
第二步:查询成绩表,并join关联学生表和课程表,以及临时结果表t1,获取综合信息,最后按照t1中的平均成绩进行order by desc倒序排列。
(2)查询语句。
hive>
select
si.stu_name,
ci.course_name,
sc.score,
t1.avg_score
from student_info si
left join score_info sc
on sc.stu_id=si.stu_id
left join course_info ci
on sc.course_id=ci.course_id
left join
(
select
stu_id,
avg(score) avg_score
from score_info
group by stu_id
)t1
on sc.stu_id=t1.stu_id
order by t1.avg_score desc;
(3)查询结果。
t2.stu_name t2.course_name t2.score t1.avg_score
李建国 数学 84 86.25
李建国 英语 87 86.25
李建国 体育 100 86.25
李建国 语文 74 86.25
刘爱党 体育 59 81.5

熊巧 体育 34 45.25
熊巧 英语 55 45.25
熊巧 数学 34 45.25
熊巧 语文 58 45.25
宋忠 英语 39 43.0
宋忠 语文 56 43.0
宋忠 数学 34 43.0
杨建军 NULL NULL NULL
Time taken: 20.137 seconds, Fetched: 75 row(s)

6.6 本章总结

本章的主要内容是结合前面章节讲解的基础查询语法给出的综合案例练习题,主要考察单表和多表的关联查询中,常用关键字的综合使用,其中包含了对分组聚合的大量练习。本章通过大量的基础练习案例,呈现了Hive SQL语法的基本知识点,并锻炼了读者对业务需求分析的基本思路,旨在使Hive初学者更快更好入门,为后面更复杂的Hive SQL使用和练习做准备。

8. 综合案例练习之初级函数

8.1 环境准备

本章的所有案例基于同一套电商行业的数据库表格,本节主要完成所有的表格创建和数据导入工作。

8.1.1 用户信息表

(1)用户信息表结构如表8-1所示,只展示部分数据。
表8-1 用户信息表结构
user_id(用户id) gender(性别) birthday(生日)
101 男 1990-01-01
102 女 1991-02-01
103 女 1992-03-01
104 男 1993-04-01
(2)执行以下建表语句,创建用户信息表。
hive>
DROP TABLE IF EXISTS user_info;
create table user_info(
user_id string COMMENT ‘用户id’,
gender string COMMENT ‘性别’,
birthday string COMMENT ‘生日’
) COMMENT ‘用户信息表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
(3)向用户信息表插入数据。
hive>
insert overwrite table user_info
values (‘101’, ‘男’, ‘1990-01-01’),
(‘102’, ‘女’, ‘1991-02-01’),
(‘103’, ‘女’, ‘1992-03-01’),
(‘104’, ‘男’, ‘1993-04-01’),
(‘105’, ‘女’, ‘1994-05-01’),
(‘106’, ‘男’, ‘1995-06-01’),
(‘107’, ‘女’, ‘1996-07-01’),
(‘108’, ‘男’, ‘1997-08-01’),
(‘109’, ‘女’, ‘1998-09-01’),
(‘1010’, ‘男’, ‘1999-10-01’),
(‘1011’, ‘男’, ‘1990-01-01’),
(‘1012’, ‘女’, ‘1991-11-11’);

8.1.2 商品信息表

(1)商品信息表结构如表8-1所示。
表8-2 商品信息表结构
sku_id(商品id) name(商品名称) category_id(分类id) from_date(上架日期) price(商品价格)
1 xiaomi 10 1 2020-01-01 2000
6 洗碗机 2 2020-02-01 2000
9 自行车 3 2020-01-01 1000
(2)执行以下建表语句,创建商品信息表。
hive>
DROP TABLE IF EXISTS sku_info;
CREATE TABLE sku_info(
sku_id string COMMENT ‘商品id’,
name string COMMENT ‘商品名称’,
category_id string COMMENT ‘所属分类id’,
from_date string COMMENT ‘上架日期’,
price double COMMENT ‘商品单价’
) COMMENT ‘商品信息表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
(3)向商品信息表插入数据。
hive>
insert overwrite table sku_info
values (‘1’, ‘xiaomi 10’, ‘1’, ‘2020-01-01’, 2000),
(‘2’, ‘手机壳’, ‘1’, ‘2020-02-01’, 10),
(‘3’, ‘apple 12’, ‘1’, ‘2020-03-01’, 5000),
(‘4’, ‘xiaomi 13’, ‘1’, ‘2020-04-01’, 6000),
(‘5’, ‘破壁机’, ‘2’, ‘2020-01-01’, 500),
(‘6’, ‘洗碗机’, ‘2’, ‘2020-02-01’, 2000),
(‘7’, ‘热水壶’, ‘2’, ‘2020-03-01’, 100),
(‘8’, ‘微波炉’, ‘2’, ‘2020-04-01’, 600),
(‘9’, ‘自行车’, ‘3’, ‘2020-01-01’, 1000),
(‘10’, ‘帐篷’, ‘3’, ‘2020-02-01’, 100),
(‘11’, ‘烧烤架’, ‘3’, ‘2020-02-01’, 50),
(‘12’, ‘遮阳伞’, ‘3’, ‘2020-03-01’, 20),
(‘13’, ‘长粒香’, ‘2’, ‘2020-01-01’, 20.0),
(‘14’, ‘金龙鱼’, ‘2’, ‘2021-01-01’, 20.0),
(‘15’, ‘巧乐兹’, ‘2’, ‘2020-01-01’, 20.0),
(‘16’, ‘费列罗’, ‘2’, ‘2022-01-01’, 20.0);

8.1.3 商品分类信息表

(1)商品分类信息表结构如表8-1所示。
表8-3 商品分类信息表结构
category_id(分类id) category_name(分类名称)
1 数码
2 厨卫
3 户外
(2)执行以下建表语句,创建商品分类信息表。
hive>
DROP TABLE IF EXISTS category_info;
create table category_info(
category_id string,
category_name string
) COMMENT ‘商品分类信息表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
(3)向商品分类信息表插入数据。
hive>
insert overwrite table category_info
values (‘1’,‘数码’),
(‘2’,‘厨卫’),
(‘3’,‘户外’);

8.1.4 订单信息表

(1)订单信息表结构如表8-1所示。
表8-4 订单信息表结构
order_id(订单id) user_id(用户id) create_date(下单日期) total_amount(订单金额)
1 101 2021-09-30 29000.00
10 103 2020-10-02 28000.00
(2)执行以下建表语句,创建订单信息表。
hive>
DROP TABLE IF EXISTS order_info;
create table order_info(
order_id string COMMENT ‘订单id’,
user_id string COMMENT ‘用户id’,
create_date string COMMENT ‘下单日期’,
total_amount decimal(16, 2) COMMENT ‘订单总金额’
) COMMENT ‘订单信息表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
(3)向订单信息表插入数据。
hive>
insert overwrite table order_info
values (‘1’, ‘101’, ‘2021-09-27’, 29000.00),
(‘2’, ‘101’, ‘2021-09-28’, 70500.00),
(‘3’, ‘101’, ‘2021-09-29’, 43300.00),
(‘4’, ‘101’, ‘2021-09-30’, 860.00),
(‘5’, ‘102’, ‘2021-10-01’, 46180.00),
(‘6’, ‘102’, ‘2021-10-01’, 50000.00),
(‘7’, ‘102’, ‘2021-10-01’, 75500.00),
(‘8’, ‘102’, ‘2021-10-02’, 6170.00),
(‘9’, ‘103’, ‘2021-10-02’, 18580.00),
(‘10’, ‘103’, ‘2021-10-02’, 28000.00),
(‘11’, ‘103’, ‘2021-10-02’, 23400.00),
(‘12’, ‘103’, ‘2021-10-03’, 5910.00),
(‘13’, ‘104’, ‘2021-10-03’, 13000.00),
(‘14’, ‘104’, ‘2021-10-03’, 69500.00),
(‘15’, ‘104’, ‘2021-10-03’, 2000.00),
(‘16’, ‘104’, ‘2021-10-03’, 5380.00),
(‘17’, ‘105’, ‘2021-10-04’, 6210.00),
(‘18’, ‘105’, ‘2021-10-04’, 68000.00),
(‘19’, ‘105’, ‘2021-10-04’, 43100.00),
(‘20’, ‘105’, ‘2021-10-04’, 2790.00),
(‘21’, ‘106’, ‘2021-10-04’, 9390.00),
(‘22’, ‘106’, ‘2021-10-05’, 58000.00),
(‘23’, ‘106’, ‘2021-10-05’, 46600.00),
(‘24’, ‘106’, ‘2021-10-05’, 5160.00),
(‘25’, ‘107’, ‘2021-10-05’, 55350.00),
(‘26’, ‘107’, ‘2021-10-05’, 14500.00),
(‘27’, ‘107’, ‘2021-10-06’, 47400.00),
(‘28’, ‘107’, ‘2021-10-06’, 6900.00),
(‘29’, ‘108’, ‘2021-10-06’, 56570.00),
(‘30’, ‘108’, ‘2021-10-06’, 44500.00),
(‘31’, ‘108’, ‘2021-10-07’, 50800.00),
(‘32’, ‘108’, ‘2021-10-07’, 3900.00),
(‘33’, ‘109’, ‘2021-10-07’, 41480.00),
(‘34’, ‘109’, ‘2021-10-07’, 88000.00),
(‘35’, ‘109’, ‘2020-10-08’, 15000.00),
(‘36’, ‘109’, ‘2020-10-08’, 9020.00),
(‘37’, ‘1010’, ‘2020-10-08’, 9260.00),
(‘38’, ‘1010’, ‘2020-10-08’, 12000.00),
(‘39’, ‘1010’, ‘2020-10-08’, 23900.00),
(‘40’, ‘1010’, ‘2020-10-08’, 6790.00),
(‘41’, ‘101’, ‘2020-10-08’, 300.00),
(‘42’, ‘101’, ‘2021-01-01’, 260.00),
(‘43’, ‘101’, ‘2021-01-02’, 280.00),
(‘44’, ‘101’, ‘2021-01-03’, 420.00),
(‘45’, ‘101’, ‘2021-01-04’, 240.00),
(‘46’, ‘1011’, ‘2021-09-26’, 240.00),
(‘47’, ‘1011’, ‘2021-10-24’, 240.00),
(‘48’, ‘1011’, ‘2022-09-24’, 240.00),
(‘49’, ‘1012’, ‘2022-09-24’, 2010.00);

8.1.5 订单明细表

(1)订单明细表结构如表8-1所示。
表8-5 订单明细表结构
order_detail_id(订单明细id) order_id(订单id) sku_id(商品id) create_date(下单日期) price(商品单价) sku_num(商品件数)
1 1 1 2021-09-30 2000.00 2
2 1 3 2021-09-30 5000.00 5
22 10 4 2020-10-02 6000.00 1
23 10 5 2020-10-02 500.00 24
24 10 6 2020-10-02 2000.00 5
(2)执行以下建表语句,创建订单明细表。
hive>
DROP TABLE IF EXISTS order_detail;
CREATE TABLE order_detail
(
order_detail_id string COMMENT ‘订单明细id’,
order_id string COMMENT ‘订单id’,
sku_id string COMMENT ‘商品id’,
create_date string COMMENT ‘下单日期’,
price decimal(16, 2) COMMENT ‘下单时的商品单价’,
sku_num int COMMENT ‘下单商品件数’
) COMMENT ‘订单明细表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
(3)向订单明细表插入数据。
hive>
INSERT overwrite table order_detail
values (‘1’, ‘1’, ‘1’, ‘2021-09-27’, 2000.00, 2),
(‘2’, ‘1’, ‘3’, ‘2021-09-27’, 5000.00, 5),
(‘3’, ‘2’, ‘4’, ‘2021-09-28’, 6000.00, 9),
(‘4’, ‘2’, ‘5’, ‘2021-09-28’, 500.00, 33),
(‘5’, ‘3’, ‘7’, ‘2021-09-29’, 100.00, 37),
(‘6’, ‘3’, ‘8’, ‘2021-09-29’, 600.00, 46),
(‘7’, ‘3’, ‘9’, ‘2021-09-29’, 1000.00, 12),
(‘8’, ‘4’, ‘12’, ‘2021-09-30’, 20.00, 43),
(‘9’, ‘5’, ‘1’, ‘2021-10-01’, 2000.00, 8),
(‘10’, ‘5’, ‘2’, ‘2021-10-01’, 10.00, 18),
(‘11’, ‘5’, ‘3’, ‘2021-10-01’, 5000.00, 6),
(‘12’, ‘6’, ‘4’, ‘2021-10-01’, 6000.00, 8),
(‘13’, ‘6’, ‘6’, ‘2021-10-01’, 2000.00, 1),
(‘14’, ‘7’, ‘7’, ‘2021-10-01’, 100.00, 17),
(‘15’, ‘7’, ‘8’, ‘2021-10-01’, 600.00, 48),
(‘16’, ‘7’, ‘9’, ‘2021-10-01’, 1000.00, 45),
(‘17’, ‘8’, ‘10’, ‘2021-10-02’, 100.00, 48),
(‘18’, ‘8’, ‘11’, ‘2021-10-02’, 50.00, 15),
(‘19’, ‘8’, ‘12’, ‘2021-10-02’, 20.00, 31),
(‘20’, ‘9’, ‘1’, ‘2021-09-30’, 2000.00, 9),
(‘21’, ‘9’, ‘2’, ‘2021-10-02’, 10.00, 5800),
(‘22’, ‘10’, ‘4’, ‘2021-10-02’, 6000.00, 1),
(‘23’, ‘10’, ‘5’, ‘2021-10-02’, 500.00, 24),
(‘24’, ‘10’, ‘6’, ‘2021-10-02’, 2000.00, 5),
(‘25’, ‘11’, ‘8’, ‘2021-10-02’, 600.00, 39),
(‘26’, ‘12’, ‘10’, ‘2021-10-03’, 100.00, 47),
(‘27’, ‘12’, ‘11’, ‘2021-10-03’, 50.00, 19),
(‘28’, ‘12’, ‘12’, ‘2021-10-03’, 20.00, 13000),
(‘29’, ‘13’, ‘1’, ‘2021-10-03’, 2000.00, 4),
(‘30’, ‘13’, ‘3’, ‘2021-10-03’, 5000.00, 1),
(‘31’, ‘14’, ‘4’, ‘2021-10-03’, 6000.00, 5),
(‘32’, ‘14’, ‘5’, ‘2021-10-03’, 500.00, 47),
(‘33’, ‘14’, ‘6’, ‘2021-10-03’, 2000.00, 8),
(‘34’, ‘15’, ‘7’, ‘2021-10-03’, 100.00, 20),
(‘35’, ‘16’, ‘10’, ‘2021-10-03’, 100.00, 22),
(‘36’, ‘16’, ‘11’, ‘2021-10-03’, 50.00, 42),
(‘37’, ‘16’, ‘12’, ‘2021-10-03’, 20.00, 7400),
(‘38’, ‘17’, ‘1’, ‘2021-10-04’, 2000.00, 3),
(‘39’, ‘17’, ‘2’, ‘2021-10-04’, 10.00, 21),
(‘40’, ‘18’, ‘4’, ‘2021-10-04’, 6000.00, 8),
(‘41’, ‘18’, ‘5’, ‘2021-10-04’, 500.00, 28),
(‘42’, ‘18’, ‘6’, ‘2021-10-04’, 2000.00, 3),
(‘43’, ‘19’, ‘7’, ‘2021-10-04’, 100.00, 55),
(‘44’, ‘19’, ‘8’, ‘2021-10-04’, 600.00, 11),
(‘45’, ‘19’, ‘9’, ‘2021-10-04’, 1000.00, 31),
(‘46’, ‘20’, ‘11’, ‘2021-10-04’, 50.00, 45),
(‘47’, ‘20’, ‘12’, ‘2021-10-04’, 20.00, 27),
(‘48’, ‘21’, ‘1’, ‘2021-10-04’, 2000.00, 2),
(‘49’, ‘21’, ‘2’, ‘2021-10-04’, 10.00, 39),
(‘50’, ‘21’, ‘3’, ‘2021-10-04’, 5000.00, 1),
(‘51’, ‘22’, ‘4’, ‘2021-10-05’, 6000.00, 8),
(‘52’, ‘22’, ‘5’, ‘2021-10-05’, 500.00, 20),
(‘53’, ‘23’, ‘7’, ‘2021-10-05’, 100.00, 58),
(‘54’, ‘23’, ‘8’, ‘2021-10-05’, 600.00, 18),
(‘55’, ‘23’, ‘9’, ‘2021-10-05’, 1000.00, 30),
(‘56’, ‘24’, ‘10’, ‘2021-10-05’, 100.00, 27),
(‘57’, ‘24’, ‘11’, ‘2021-10-05’, 50.00, 28),
(‘58’, ‘24’, ‘12’, ‘2021-10-05’, 20.00, 53),
(‘59’, ‘25’, ‘1’, ‘2021-10-05’, 2000.00, 5),
(‘60’, ‘25’, ‘2’, ‘2021-10-05’, 10.00, 35),
(‘61’, ‘25’, ‘3’, ‘2021-10-05’, 5000.00, 9),
(‘62’, ‘26’, ‘4’, ‘2021-10-05’, 6000.00, 1),
(‘63’, ‘26’, ‘5’, ‘2021-10-05’, 500.00, 13),
(‘64’, ‘26’, ‘6’, ‘2021-10-05’, 2000.00, 1),
(‘65’, ‘27’, ‘7’, ‘2021-10-06’, 100.00, 30),
(‘66’, ‘27’, ‘8’, ‘2021-10-06’, 600.00, 19),
(‘67’, ‘27’, ‘9’, ‘2021-10-06’, 1000.00, 33),
(‘68’, ‘28’, ‘10’, ‘2021-10-06’, 100.00, 37),
(‘69’, ‘28’, ‘11’, ‘2021-10-06’, 50.00, 46),
(‘70’, ‘28’, ‘12’, ‘2021-10-06’, 20.00, 45),
(‘71’, ‘29’, ‘1’, ‘2021-10-06’, 2000.00, 8),
(‘72’, ‘29’, ‘2’, ‘2021-10-06’, 10.00, 57),
(‘73’, ‘29’, ‘3’, ‘2021-10-06’, 5000.00, 8),
(‘74’, ‘30’, ‘4’, ‘2021-10-06’, 6000.00, 3),
(‘75’, ‘30’, ‘5’, ‘2021-10-06’, 500.00, 33),
(‘76’, ‘30’, ‘6’, ‘2021-10-06’, 2000.00, 5),
(‘77’, ‘31’, ‘8’, ‘2021-10-07’, 600.00, 13),
(‘78’, ‘31’, ‘9’, ‘2021-10-07’, 1000.00, 43),
(‘79’, ‘32’, ‘10’, ‘2021-10-07’, 100.00, 24),
(‘80’, ‘32’, ‘11’, ‘2021-10-07’, 50.00, 30),
(‘81’, ‘33’, ‘1’, ‘2021-10-07’, 2000.00, 8),
(‘82’, ‘33’, ‘2’, ‘2021-10-07’, 10.00, 48),
(‘83’, ‘33’, ‘3’, ‘2021-10-07’, 5000.00, 5),
(‘84’, ‘34’, ‘4’, ‘2021-10-07’, 6000.00, 10),
(‘85’, ‘34’, ‘5’, ‘2021-10-07’, 500.00, 44),
(‘86’, ‘34’, ‘6’, ‘2021-10-07’, 2000.00, 3),
(‘87’, ‘35’, ‘8’, ‘2020-10-08’, 600.00, 25),
(‘88’, ‘36’, ‘10’, ‘2020-10-08’, 100.00, 57),
(‘89’, ‘36’, ‘11’, ‘2020-10-08’, 50.00, 44),
(‘90’, ‘36’, ‘12’, ‘2020-10-08’, 20.00, 56),
(‘91’, ‘37’, ‘1’, ‘2020-10-08’, 2000.00, 2),
(‘92’, ‘37’, ‘2’, ‘2020-10-08’, 10.00, 26),
(‘93’, ‘37’, ‘3’, ‘2020-10-08’, 5000.00, 1),
(‘94’, ‘38’, ‘6’, ‘2020-10-08’, 2000.00, 6),
(‘95’, ‘39’, ‘7’, ‘2020-10-08’, 100.00, 35),
(‘96’, ‘39’, ‘8’, ‘2020-10-08’, 600.00, 34),
(‘97’, ‘40’, ‘10’, ‘2020-10-08’, 100.00, 37),
(‘98’, ‘40’, ‘11’, ‘2020-10-08’, 50.00, 51),
(‘99’, ‘40’, ‘12’, ‘2020-10-08’, 20.00, 27),
(‘100’, ‘41’, ‘15’, ‘2020-10-08’, 300.00, 15),
(‘101’, ‘42’, ‘13’, ‘2021-01-01’, 260.00, 13),
(‘102’, ‘43’, ‘13’, ‘2021-01-02’, 280.00, 14),
(‘103’, ‘44’, ‘14’, ‘2021-01-03’, 420.00, 21),
(‘104’, ‘45’, ‘14’, ‘2021-01-04’, 240.00, 12),
(‘105’, ‘46’, ‘14’, ‘2021-09-26’, 240.00, 12),
(‘106’, ‘47’, ‘14’, ‘2021-10-24’, 240.00, 12),
(‘107’, ‘48’, ‘14’, ‘2022-09-24’, 240.00, 12),
(‘108’, ‘49’, ‘1’, ‘2022-09-24’, 2000.00, 1),
(‘109’, ‘49’, ‘2’, ‘2022-09-24’, 10.00, 1);

8.1.6 登录明细表

(1)登录明细表结构如表8-1所示。
表8-6 登录明细表结构
user_id(用户id) ip_address(ip地址) login_ts(登录时间) logout_ts(登出时间)
101 180.149.130.161 2021-09-21 08:00:00 2021-09-27 08:30:00
102 120.245.11.2 2021-09-22 09:00:00 2021-09-27 09:30:00
103 27.184.97.3 2021-09-23 10:00:00 2021-09-27 10:30:00
(2)执行以下建表语句,创建登录明细表。
hive>
DROP TABLE IF EXISTS user_login_detail;
CREATE TABLE user_login_detail
(
user_id string comment ‘用户id’,
ip_address string comment ‘ip地址’,
login_ts string comment ‘登录时间’,
logout_ts string comment ‘登出时间’
) COMMENT ‘登录明细表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
(3)向登录明细表插入数据。
hive>
INSERT overwrite table user_login_detail
VALUES (‘101’, ‘180.149.130.161’, ‘2021-09-21 08:00:00’, ‘2021-09-27 08:30:00’),
(‘101’, ‘180.149.130.161’, ‘2021-09-27 08:00:00’, ‘2021-09-27 08:30:00’),
(‘101’, ‘180.149.130.161’, ‘2021-09-28 09:00:00’, ‘2021-09-28 09:10:00’),
(‘101’, ‘180.149.130.161’, ‘2021-09-29 13:30:00’, ‘2021-09-29 13:50:00’),
(‘101’, ‘180.149.130.161’, ‘2021-09-30 20:00:00’, ‘2021-09-30 20:10:00’),
(‘102’, ‘120.245.11.2’, ‘2021-09-22 09:00:00’, ‘2021-09-27 09:30:00’),
(‘102’, ‘120.245.11.2’, ‘2021-10-01 08:00:00’, ‘2021-10-01 08:30:00’),
(‘102’, ‘180.149.130.174’, ‘2021-10-01 07:50:00’, ‘2021-10-01 08:20:00’),
(‘102’, ‘120.245.11.2’, ‘2021-10-02 08:00:00’, ‘2021-10-02 08:30:00’),
(‘103’, ‘27.184.97.3’, ‘2021-09-23 10:00:00’, ‘2021-09-27 10:30:00’),
(‘103’, ‘27.184.97.3’, ‘2021-10-03 07:50:00’, ‘2021-10-03 09:20:00’),
(‘104’, ‘27.184.97.34’, ‘2021-09-24 11:00:00’, ‘2021-09-27 11:30:00’),
(‘104’, ‘27.184.97.34’, ‘2021-10-03 07:50:00’, ‘2021-10-03 08:20:00’),
(‘104’, ‘27.184.97.34’, ‘2021-10-03 08:50:00’, ‘2021-10-03 10:20:00’),
(‘104’, ‘120.245.11.89’, ‘2021-10-03 08:40:00’, ‘2021-10-03 10:30:00’),
(‘105’, ‘119.180.192.212’, ‘2021-10-04 09:10:00’, ‘2021-10-04 09:30:00’),
(‘106’, ‘119.180.192.66’, ‘2021-10-04 08:40:00’, ‘2021-10-04 10:30:00’),
(‘106’, ‘119.180.192.66’, ‘2021-10-05 21:50:00’, ‘2021-10-05 22:40:00’),
(‘107’, ‘219.134.104.7’, ‘2021-09-25 12:00:00’, ‘2021-09-27 12:30:00’),
(‘107’, ‘219.134.104.7’, ‘2021-10-05 22:00:00’, ‘2021-10-05 23:00:00’),
(‘107’, ‘219.134.104.7’, ‘2021-10-06 09:10:00’, ‘2021-10-06 10:20:00’),
(‘107’, ‘27.184.97.46’, ‘2021-10-06 09:00:00’, ‘2021-10-06 10:00:00’),
(‘108’, ‘101.227.131.22’, ‘2021-10-06 09:00:00’, ‘2021-10-06 10:00:00’),
(‘108’, ‘101.227.131.22’, ‘2021-10-06 22:00:00’, ‘2021-10-06 23:00:00’),
(‘109’, ‘101.227.131.29’, ‘2021-09-26 13:00:00’, ‘2021-09-27 13:30:00’),
(‘109’, ‘101.227.131.29’, ‘2021-10-06 08:50:00’, ‘2021-10-06 10:20:00’),
(‘109’, ‘101.227.131.29’, ‘2021-10-08 09:00:00’, ‘2021-10-08 09:10:00’),
(‘1010’, ‘119.180.192.10’, ‘2021-09-27 14:00:00’, ‘2021-09-27 14:30:00’),
(‘1010’, ‘119.180.192.10’, ‘2021-10-09 08:50:00’, ‘2021-10-09 10:20:00’),
(‘1011’, ‘180.149.130.161’, ‘2021-09-21 08:00:00’, ‘2021-09-27 08:30:00’),
(‘1011’, ‘180.149.130.161’, ‘2021-10-24 08:00:00’, ‘2021-10-29 08:30:00’),
(‘1011’, ‘180.149.130.161’, ‘2022-09-21 08:00:00’, ‘2022-09-27 08:30:00’),
(‘105’, ‘119.180.192.212’, ‘2021-10-06 09:10:00’, ‘2021-10-04 09:30:00’),
(‘106’, ‘119.180.192.66’, ‘2021-10-05 08:50:00’, ‘2021-10-05 12:40:00’);

8.1.7 商品价格变更明细表

(1)商品价格变更明细表结构如表8-1所示。
表8-7 商品价格变更明细表结构
sku_id(商品id) new_price(本次变更之后的价格) change_date(变更日期)
1 1900.00 2021-09-25
1 2000.00 2021-09-26
2 80.00 2021-09-29
2 10.00 2021-09-30
(2)执行以下建表语句,创建商品价格变更明细表。
hive>
DROP TABLE IF EXISTS sku_price_modify_detail;
CREATE TABLE sku_price_modify_detail
(
sku_id string comment ‘商品id’,
new_price decimal(16, 2) comment ‘更改后的价格’,
change_date string comment ‘变动日期’
) COMMENT ‘商品价格变更明细表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
(3)向商品价格变更明细表插入数据。
hive>
insert overwrite table sku_price_modify_detail
values (‘1’, 1900, ‘2021-09-25’),
(‘1’, 2000, ‘2021-09-26’),
(‘2’, 80, ‘2021-09-29’),
(‘2’, 10, ‘2021-09-30’),
(‘3’, 4999, ‘2021-09-25’),
(‘3’, 5000, ‘2021-09-26’),
(‘4’, 5600, ‘2021-09-26’),
(‘4’, 6000, ‘2021-09-27’),
(‘5’, 490, ‘2021-09-27’),
(‘5’, 500, ‘2021-09-28’),
(‘6’, 1988, ‘2021-09-30’),
(‘6’, 2000, ‘2021-10-01’),
(‘7’, 88, ‘2021-09-28’),
(‘7’, 100, ‘2021-09-29’),
(‘8’, 800, ‘2021-09-28’),
(‘8’, 600, ‘2021-09-29’),
(‘9’, 1100, ‘2021-09-27’),
(‘9’, 1000, ‘2021-09-28’),
(‘10’, 90, ‘2021-10-01’),
(‘10’, 100, ‘2021-10-02’),
(‘11’, 66, ‘2021-10-01’),
(‘11’, 50, ‘2021-10-02’),
(‘12’, 35, ‘2021-09-28’),
(‘12’, 20, ‘2021-09-29’);

8.1.8 配送信息表

(1)配送信息表结构如表8-1所示。
表8-8 配送信息表结构
delivery_id(运单id) order_id(订单id) user_id(用户id) order_date(下单日期) custom_date(期望配送日期)
1 1 101 2021-09-27 2021-09-29
2 2 101 2021-09-28 2021-09-28
3 3 101 2021-09-29 2021-09-30
(2)执行以下建表语句,创建配送信息表。
hive>
DROP TABLE IF EXISTS delivery_info;
CREATE TABLE delivery_info
(
delivery_id string comment ‘配送单id’,
order_id string comment ‘订单id’,
user_id string comment ‘用户id’,
order_date string comment ‘下单日期’,
custom_date string comment ‘期望配送日期’
) COMMENT ‘配送信息表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
(3)向配送信息表插入数据。
hive>
insert overwrite table delivery_info
values (‘1’, ‘1’, ‘101’, ‘2021-09-27’, ‘2021-09-29’),
(‘2’, ‘2’, ‘101’, ‘2021-09-28’, ‘2021-09-28’),
(‘3’, ‘3’, ‘101’, ‘2021-09-29’, ‘2021-09-30’),
(‘4’, ‘4’, ‘101’, ‘2021-09-30’, ‘2021-10-01’),
(‘5’, ‘5’, ‘102’, ‘2021-10-01’, ‘2021-10-01’),
(‘6’, ‘6’, ‘102’, ‘2021-10-01’, ‘2021-10-01’),
(‘7’, ‘7’, ‘102’, ‘2021-10-01’, ‘2021-10-03’),
(‘8’, ‘8’, ‘102’, ‘2021-10-02’, ‘2021-10-02’),
(‘9’, ‘9’, ‘103’, ‘2021-10-02’, ‘2021-10-03’),
(‘10’, ‘10’, ‘103’, ‘2021-10-02’, ‘2021-10-04’),
(‘11’, ‘11’, ‘103’, ‘2021-10-02’, ‘2021-10-02’),
(‘12’, ‘12’, ‘103’, ‘2021-10-03’, ‘2021-10-03’),
(‘13’, ‘13’, ‘104’, ‘2021-10-03’, ‘2021-10-04’),
(‘14’, ‘14’, ‘104’, ‘2021-10-03’, ‘2021-10-04’),
(‘15’, ‘15’, ‘104’, ‘2021-10-03’, ‘2021-10-03’),
(‘16’, ‘16’, ‘104’, ‘2021-10-03’, ‘2021-10-03’),
(‘17’, ‘17’, ‘105’, ‘2021-10-04’, ‘2021-10-04’),
(‘18’, ‘18’, ‘105’, ‘2021-10-04’, ‘2021-10-06’),
(‘19’, ‘19’, ‘105’, ‘2021-10-04’, ‘2021-10-06’),
(‘20’, ‘20’, ‘105’, ‘2021-10-04’, ‘2021-10-04’),
(‘21’, ‘21’, ‘106’, ‘2021-10-04’, ‘2021-10-04’),
(‘22’, ‘22’, ‘106’, ‘2021-10-05’, ‘2021-10-05’),
(‘23’, ‘23’, ‘106’, ‘2021-10-05’, ‘2021-10-05’),
(‘24’, ‘24’, ‘106’, ‘2021-10-05’, ‘2021-10-07’),
(‘25’, ‘25’, ‘107’, ‘2021-10-05’, ‘2021-10-05’),
(‘26’, ‘26’, ‘107’, ‘2021-10-05’, ‘2021-10-06’),
(‘27’, ‘27’, ‘107’, ‘2021-10-06’, ‘2021-10-06’),
(‘28’, ‘28’, ‘107’, ‘2021-10-06’, ‘2021-10-07’),
(‘29’, ‘29’, ‘108’, ‘2021-10-06’, ‘2021-10-06’),
(‘30’, ‘30’, ‘108’, ‘2021-10-06’, ‘2021-10-06’),
(‘31’, ‘31’, ‘108’, ‘2021-10-07’, ‘2021-10-09’),
(‘32’, ‘32’, ‘108’, ‘2021-10-07’, ‘2021-10-09’),
(‘33’, ‘33’, ‘109’, ‘2021-10-07’, ‘2021-10-08’),
(‘34’, ‘34’, ‘109’, ‘2021-10-07’, ‘2021-10-08’),
(‘35’, ‘35’, ‘109’, ‘2021-10-08’, ‘2021-10-10’),
(‘36’, ‘36’, ‘109’, ‘2021-10-08’, ‘2021-10-09’),
(‘37’, ‘37’, ‘1010’, ‘2021-10-08’, ‘2021-10-10’),
(‘38’, ‘38’, ‘1010’, ‘2021-10-08’, ‘2021-10-10’),
(‘39’, ‘39’, ‘1010’, ‘2021-10-08’, ‘2021-10-09’),
(‘40’, ‘40’, ‘1010’, ‘2021-10-08’, ‘2021-10-09’);

8.1.9 好友关系表

(1)好友关系表结构如表8-1所示。
表8-9 好友关系表结构
user1_id(用户1 id) user2_id(用户2 id)
101 1010
101 108
101 106
注意:表中一行数据中的两个user_id,表示两个用户互为好友。
(2)执行以下建表语句,创建好友关系表。
hive>
DROP TABLE IF EXISTS friendship_info;
CREATE TABLE friendship_info(
user1_id string comment ‘用户1id’,
user2_id string comment ‘用户2id’
) COMMENT ‘好友关系表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
(3)向好友关系表插入数据。
hive>
insert overwrite table friendship_info
values (‘101’, ‘1010’),
(‘101’, ‘108’),
(‘101’, ‘106’),
(‘101’, ‘104’),
(‘101’, ‘102’),
(‘102’, ‘1010’),
(‘102’, ‘108’),
(‘102’, ‘106’),
(‘102’, ‘104’),
(‘103’, ‘1010’),
(‘103’, ‘108’),
(‘103’, ‘106’),
(‘103’, ‘104’),
(‘103’, ‘102’),
(‘104’, ‘1010’),
(‘104’, ‘108’),
(‘104’, ‘106’),
(‘104’, ‘102’),
(‘105’, ‘1010’),
(‘105’, ‘108’),
(‘105’, ‘106’),
(‘105’, ‘104’),
(‘105’, ‘102’),
(‘106’, ‘1010’),
(‘106’, ‘108’),
(‘106’, ‘104’),
(‘106’, ‘102’),
(‘107’, ‘1010’),
(‘107’, ‘108’),
(‘107’, ‘106’),
(‘107’, ‘104’),
(‘107’, ‘102’),
(‘108’, ‘1010’),
(‘108’, ‘106’),
(‘108’, ‘104’),
(‘108’, ‘102’),
(‘109’, ‘1010’),
(‘109’, ‘108’),
(‘109’, ‘106’),
(‘109’, ‘104’),
(‘109’, ‘102’),
(‘1010’, ‘108’),
(‘1010’, ‘106’),
(‘1010’, ‘104’),
(‘1010’, ‘102’),
(‘101’, ‘1011’);

8.1.10 收藏信息表

(1)收藏信息表结构如表8-1所示。
表8-10 收藏信息表结构
user_id(用户id) sku_id(商品id) create_date(收藏日期)
101 3 2021-09-23
101 12 2021-09-23
101 6 2021-09-25
(2)执行以下建表语句,创建收藏信息表。
hive>
DROP TABLE IF EXISTS favor_info;
CREATE TABLE favor_info
(
user_id string comment ‘用户id’,
sku_id string comment ‘商品id’,
create_date string comment ‘收藏日期’
) COMMENT ‘收藏信息表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
(3)向收藏信息表插入数据。
hive>
insert overwrite table favor_info
values (‘101’, ‘3’, ‘2021-09-23’),
(‘101’, ‘12’, ‘2021-09-23’),
(‘101’, ‘6’, ‘2021-09-25’),
(‘101’, ‘10’, ‘2021-09-21’),
(‘101’, ‘5’, ‘2021-09-25’),
(‘102’, ‘1’, ‘2021-09-24’),
(‘102’, ‘2’, ‘2021-09-24’),
(‘102’, ‘8’, ‘2021-09-23’),
(‘102’, ‘12’, ‘2021-09-22’),
(‘102’, ‘11’, ‘2021-09-23’),
(‘102’, ‘9’, ‘2021-09-25’),
(‘102’, ‘4’, ‘2021-09-25’),
(‘102’, ‘6’, ‘2021-09-23’),
(‘102’, ‘7’, ‘2021-09-26’),
(‘103’, ‘8’, ‘2021-09-24’),
(‘103’, ‘5’, ‘2021-09-25’),
(‘103’, ‘6’, ‘2021-09-26’),
(‘103’, ‘12’, ‘2021-09-27’),
(‘103’, ‘7’, ‘2021-09-25’),
(‘103’, ‘10’, ‘2021-09-25’),
(‘103’, ‘4’, ‘2021-09-24’),
(‘103’, ‘11’, ‘2021-09-25’),
(‘103’, ‘3’, ‘2021-09-27’),
(‘104’, ‘9’, ‘2021-09-28’),
(‘104’, ‘7’, ‘2021-09-28’),
(‘104’, ‘8’, ‘2021-09-25’),
(‘104’, ‘3’, ‘2021-09-28’),
(‘104’, ‘11’, ‘2021-09-25’),
(‘104’, ‘6’, ‘2021-09-25’),
(‘104’, ‘12’, ‘2021-09-28’),
(‘105’, ‘8’, ‘2021-10-08’),
(‘105’, ‘9’, ‘2021-10-07’),
(‘105’, ‘7’, ‘2021-10-07’),
(‘105’, ‘11’, ‘2021-10-06’),
(‘105’, ‘5’, ‘2021-10-07’),
(‘105’, ‘4’, ‘2021-10-05’),
(‘105’, ‘10’, ‘2021-10-07’),
(‘106’, ‘12’, ‘2021-10-08’),
(‘106’, ‘1’, ‘2021-10-08’),
(‘106’, ‘4’, ‘2021-10-04’),
(‘106’, ‘5’, ‘2021-10-08’),
(‘106’, ‘2’, ‘2021-10-04’),
(‘106’, ‘6’, ‘2021-10-04’),
(‘106’, ‘7’, ‘2021-10-08’),
(‘107’, ‘5’, ‘2021-09-29’),
(‘107’, ‘3’, ‘2021-09-28’),
(‘107’, ‘10’, ‘2021-09-27’),
(‘108’, ‘9’, ‘2021-10-08’),
(‘108’, ‘3’, ‘2021-10-10’),
(‘108’, ‘8’, ‘2021-10-10’),
(‘108’, ‘10’, ‘2021-10-07’),
(‘108’, ‘11’, ‘2021-10-07’),
(‘109’, ‘2’, ‘2021-09-27’),
(‘109’, ‘4’, ‘2021-09-29’),
(‘109’, ‘5’, ‘2021-09-29’),
(‘109’, ‘9’, ‘2021-09-30’),
(‘109’, ‘8’, ‘2021-09-26’),
(‘1010’, ‘2’, ‘2021-09-29’),
(‘1010’, ‘9’, ‘2021-09-29’),
(‘1010’, ‘1’, ‘2021-10-01’),
(‘1012’, ‘3’, ‘2021-09-23’),
(‘1010’, ‘13’, ‘2021-09-23’);

8.2 初级函数练习

8.2.1 筛选2021年总销量小于100的商品

  1. 题目需求

从订单明细表(order_detail)中筛选出2021年总销量小于100的商品及其销量,假设今天的日期是2022-01-10,不考虑上架时间小于一个月的商品,期望结果如表8-11所示。
表8-11 去年总销量小于100的商品
sku_id

(商品id) name

(商品名称) order_num

(销量)
1 xiaomi 10 49
3 apple 12 35
4 xiaomi 13 53
6 洗碗机 26
13 长粒香 27
14 金龙鱼 57

  1. 思路分析
    1)知识储备
    (1)year(string datestr)函数:参数datestr是格式化日期字符串,返回值为日期所属年份,int类型。
    (2)datediff(string enddate, string startdate)函数:startdate和enddate均为yyyy-MM-dd格式的日期字符串,返回值为二者的天数差,int类型。如datediff(‘2022-03-01’, ‘2022-02-21’)返回值为8。
    2)执行步骤
    目标数据需要满足三个条件:
    条件一:下单操作发生在2021年
    条件二:2021年总销量小于100
    条件三:上架时间不小于1个月
    第一步,统计2021年总销量小于100的商品以及其销量。
    (1)t1子查询
    ① 从order_detail表中筛选2021年的下单明细。
    ② 按照sku_id分组,通过sum(sku_num)统计每个sku_id的销量。
    ③ 过滤2021年销量小于100的sku。
    分组聚合之后的过滤通过having子句实现。
    第二步,筛选上架时间不小于1个月的商品
    (2)t2子查询
    从sku_info表中筛选上架时间不小于1个月的商品,通过datediff函数计算当日和上架日期的差值,保留该值大于30的记录。
    第三步,两表联查得到最终结果
    (3)关联t1、t2得到最终结果
    t1表的数据满足了条件一和条件二,t2表的数据满足了条件三,三个条件需要同时满足,因此t1表和t2表应使用内连接关联,选取sku的id、名称和销量字段。
    3)图解
  2. 代码实现
    hive>
    select t1.sku_id,
    t2.name,
    order_num
    from (select sku_id,
    sum(sku_num) order_num
    from order_detail
    where year(create_date) = 2021
    group by sku_id
    having order_num < 100) t1
    join (select sku_id,
    name
    from sku_info
    where datediff(‘2022-01-10’, from_date) > 30) t2
    on t1.sku_id = t2.sku_id;

8.2.2 查询每日新增用户数

  1. 题目需求
    从用户登录明细表(user_login_detail)中查询每天的新增用户数。若一个用户在某天登录了,且在这一天之前没登录过,则认为该用户为这一天的新增用户。期望结果如表8-12所示。
    表8-12 每日新增用户数
    login_date_first

(日期) user_count

(新增用户数)
2021-09-21 2
2021-09-22 1
2021-09-23 1
2021-09-24 1
2021-09-25 1
2021-09-26 1
2021-09-27 1
2021-10-04 2
2021-10-06 1
5. 思路分析
1)知识储备
date_format(date/timestamp/string dt, string formatstr):将date/timestamp/string类型的日期字段dt转换为formatstr格式的日期字符串。
2)执行步骤
(1)t1子查询
读取user_login_detail表,获取每个用户的首次登录日期。原表中login_ts字段为"yyyy-MM-dd HH:mm:ss"格式的日期字符串,首先调用date_format()函数将该日期数据格式化为"yyyy-MM-dd"的字符串,获取登录日期,而后按照user_id分组,选取登录日期的最小值即可获得每个用户的首次登录日期。
(2)最终结果
将t1作为数据源,按照首次登录日期分组,统计user_id的数量即为当日首次登录用户数,而user_id不为null,user_id的数量等价于数据条数,因此count()即可。
3)图解
6. 代码实现
hive>
select login_date_first,
count(
) user_count
from (select user_id,
min(date_format(login_ts, ‘yyyy-MM-dd’)) login_date_first
from user_login_detail
group by user_id
) t1
group by login_date_first;

8.2.3 用户注册、登录、下单综合统计

  1. 题目需求
    从用户登录明细表(user_login_detail)和订单信息表(order_info)中查询每个用户的注册日期(首次登录日期)、总登录次数,以及2021年的登录次数、订单数和订单总额。期望结果如表8-13所示。
    表8-13 用户注册、登录、下单综合统计
    user_id
    (用户id)
    register_date
    (注册日期)
    total_login_count
    (累积登录次数)
    login_count_2021
    (2021年登录次数)
    order_count_2021
    (2021年下单次数)
    order_amount_2021
    (2021年订单金额)
    <decimal(16,2)>
    101 2021-09-21 5 5 8 144860.00
    102 2021-09-22 4 4 4 177850.00
    103 2021-09-23 2 2 4 75890.00
    104 2021-09-24 4 4 4 89880.00
    105 2021-10-04 2 2 4 120100.00
    106 2021-10-04 3 3 4 119150.00
    107 2021-09-25 4 4 4 124150.00
    108 2021-10-06 2 2 4 155770.00
    109 2021-09-26 3 3 2 129480.00
    1010 2021-09-27 2 2 0 0.00
    1011 2021-09-21 3 2 2 480.00
  2. 思路分析
    1)知识储备
    (1)if(boolean testCondition, T1 value1, T2 value2):判断testCondition是否为真,是则返回value1,否则返回value2,value1和value2类型可以不同,且后者可以为null。
    (2)nvl(T value, T default_value):value为null则返回default_value,否则返回value。
    2)执行步骤
    (1)t1子查询
    各用户注册日期、总登录次数及2021年登录次数都是基于用户登录业务过程的指标,应从user_login_detail表统计获得,将三者的计算交给t1子查询完成。
    ① 读取user_login_detail表数据,按照user_id分组
    ② 统计用户注册日期
    调用date_foramt()函数将login_ts转化为yyyy-MM-dd格式的字符串,调用min()取其最小值即为用户的注册日期。
    ③ 统计用户登录总次数
    每条记录都对应一次登录,因此行数等于用户登录次数,因而count(1)即登录总次数。
    ④ 统计用户在2021年的登录次数
    首先通过year(login_ts)取登录时间所属年份。然后调用if((year(login_ts)=xxx, 1, null)判断登录年份,若为2021返回1,否则返回null。接着在最外层调用count()函数: count(if(year(login_ts)=2021,1,null)),而count(null)等于0,因此该语句的含义是:发生在2021年的登录操作记为1,其它年份的登录操作记为0,然后求和。其和即2021年登录次数。
    (2)t2子查询
    订单数和订单总额都是与用户下单业务过程相关的指标,order_detail和order_info表均涉及下单业务过程,此处需要用到order_id和金额字段,两张表均可满足需求,但order_detail表的粒度更细,聚合需要统计更多的数据,因此选择order_info表作为数据源。
    ① 读取order_info表数据,筛选发生在2021年的下单操作
    下单相关的统计指标只需要2021年的数据,调用year()函数通过where子句过滤即可。
    ② 按照user_id分组
    ③ 统计订单数
    订单表的粒度为一次下单记录,每个order_id都是唯一的,因此统计订单数无须去重。
    (3)最终结果
    登录未必下单,下单必须登录,因此登录用户是下单用户的超集。以t1作为主表,通过user_id左外连接t2,取目标字段即可。要注意,从表字段order_count_2021和order_amount_2021可能为null,要用nvl()函数赋默认值。
    3)图解
  3. 代码实现
    hive>
    select t1.user_id,
    register_date,
    total_login_count,
    login_count_2021,
    nvl(order_count_2021, 0) order_count_2021,
    cast(nvl(order_amount_2021, 0.0) as decimal(16, 2)) order_amount_2021
    from (select user_id,
    min(date_format(login_ts, ‘yyyy-MM-dd’)) register_date,
    count(1) total_login_count,
    count(if(year(login_ts) = 2021, 1, null)) login_count_2021
    from user_login_detail
    group by user_id) t1
    left join (select user_id,
    count(order_id) order_count_2021,
    sum(total_amount) order_amount_2021
    from order_info
    where year(create_date) = 2021
    group by user_id) t2 on t1.user_id = t2.user_id;

8.2.4 向用户推荐朋友收藏的商品

  1. 题目需求
    请向所有用户推荐其朋友收藏但是自己未收藏的商品,从好友关系表(friendship_info)和收藏表(favor_info)中查询出应向哪位用户推荐哪些商品。期望结果如表8-14所示
    1)部分结果展示
    表8-14 商品推荐
    user_id

(用户id) sku_id

(应向该用户推荐的商品id)
101 2
101 4
101 7
101 9
101 8
101 11
101 1
101 13
2)完整结果
user_id sku_id
101 2
101 4
101 7
101 9
101 8
101 11
101 1
102 3
102 5
102 10
103 2
103 1
103 9
104 1
104 4
104 10
104 5
104 2
105 1
105 2
105 6
105 12
105 3
106 11
106 10
106 8
106 9
106 3
107 11
107 7
107 4
107 9
107 12
107 1
107 8
107 6
107 2
108 2
108 6
108 12
108 1
108 7
108 4
108 5
109 6
109 10
109 7
109 1
109 12
109 3
109 11
1010 4
1010 10
1010 6
1010 12
1010 11
1010 8
1010 3
1010 5
1010 7
11. 思路分析
1)知识储备
distinct用于数据去重,通常有如下两种用法:
(1)置于select之后所有字段之前,如:select distinct user_id, login_ts from user_login_detail
(2)搭配聚合函数使用,如select distinct count(distinct user_id) from user_login_detail
2)执行步骤
本题需要用户的收藏记录及好友信息,从favor_info和friendship_info获取数据。
(1)t1子查询
t1的任务是获取所有用户的所有好友,从friendship_info表读取数据。该表有两个字段user1_id和user2_id,如果将user1_id作为用户,user2_id作为好友可能会导致数据丢失。因为user2是user1的好友,反之user1也是user2的好友,我们丢弃了user2作为用户,user1作为好友的数据。将user1和user2对调,再和原表union即可。
(2)关联收藏表获取好友收藏
这一步要通过t1表中好友的user_id匹配收藏表的user_id,将二者关联。关联结果包含三类数据,如下。
① 来自收藏表,无法在t1表找到相匹配user2_id的数据。即收藏了商品,但不是任何人好友的用户(等价于没有任何好友),不可能基于他们的收藏做任何推荐,舍弃。
② 来自t1表,无法在收藏表找到相匹配user_id的数据。即某用户的某位好友没有任何收藏,我们不可能通过这位好友向用户推荐任何商品,这类数据同样舍弃。
③ 满足关联条件的数据,即某用户的某位好友收藏了某个sku。本题正是基于这些sku向该用户推荐商品,这类数据保留。
综上,只保留第三类数据。两表通过内连接关联。获取每位用户每位好友收藏的sku_id。
(3)关联收藏表获得最终结果
注:(2)中查询结果并没有被封装为子查询,因此没有别名,此处为便于论述姑且将(2)中获取的数据集称之为t2。
这一步要通过t2的user_id及好友收藏的sku_id匹配收藏表的user_id和sku_id,将二者关联,结果同样分为三类,如下。
① 满足关联条件的数据,即本人和好友收藏了相同sku_id的数据。这类数据应舍弃。
② 来自t2表,不满足关联条件的数据。即好友收藏某sku、但本人未收藏该sku(该用户可能收藏了其它sku,也可能没有收藏记录)的记录。这部分sku_id应推荐给用户,保留此类数据。
③ 收藏表中不满足关联条件的数据。即本人收藏过但没有好友的记录或本人收藏但好友未收藏的记录,舍弃。
综上,我们需要第二类数据。以t2作为主表,left join收藏表,并过滤第一类数据(取自右表的sku_id为null即可)。
最终,我们获取了所有用户好友收藏本人未收藏的sku_id。获取t2的user_id和好友sku_id即可。此处可能有重复数据,因为用户1的不同好友可能收藏了相同的sku,若该sku本人未收藏,则会生成多条user_id和sku_id相同的数据。通过distinct去重即可获得最终结果。
3)图解
下图关联结果在SQL中没有别名,为便于分析此处命名为t2,下文同理。
将t2关联favor_info表的中间结果命名为t2’'。
12. 代码实现
hive>
select
distinct t1.user_id,
friend_favor.sku_id
from
(
select
user1_id user_id,
user2_id friend_id
from friendship_info
union
select
user2_id,
user1_id
from friendship_info
)t1
join favor_info friend_favor
on t1.friend_id=friend_favor.user_id
left join favor_info user_favor
on t1.user_id=user_favor.user_id
and friend_favor.sku_id=user_favor.sku_id
where user_favor.sku_id is null;

8.2.5 男性和女性每日的购物总金额统计

  1. 题目需求
    从订单信息表(order_info)和用户信息表(user_info)中,分别统计每天男性和女性用户的订单总金额,如果当天男性或者女性没有购物,则统计结果为0。期望结果如表8-15所示。
    表8-15 分性别购物金额统计
    create_date

(日期) total_amount_male
<decimal(16,2)>
(男性用户总金额) total_amount_female
<decimal(16,2)>
(女性用户总金额)
2020-10-08 52250.00 24020.00
2021-01-01 260.00 0.00
2021-01-02 280.00 0.00
2021-01-03 420.00 0.00
2021-01-04 240.00 0.00
2021-09-26 240.00 0.00
2021-09-27 29000.00 0.00
2021-09-28 70500.00 0.00
2021-09-29 43300.00 0.00
2021-09-30 860.00 0.00
2021-10-01 0.00 171680.00
2021-10-02 0.00 76150.00
2021-10-03 89880.00 5910.00
2021-10-04 9390.00 120100.00
2021-10-05 109760.00 69850.00
2021-10-06 101070.00 54300.00
2021-10-07 54700.00 129480.00
2021-10-24 240.00 0.00
2022-09-24 240.00 2010.00
14. 思路分析
1)知识储备
cast(expr as ):将expr的执行结果转换为类型的数据并返回,expr可以是函数(可以嵌套)、字段或字面值。转换失败返回null,对于cast(expr as boolean),对任意的非空字符串expr返回true。
2)执行步骤
(1)关联
① 首先考虑数据源,本题需要获取下单日期、订单金额、用户ID以及用户性别等信息,前三个字段可以从订单表获得,用户性别可以从用户表获得。
② 接下来考虑两张表数据的整合方式。我们要从订单表获取用户的订单信息,从用户表获取用户的性别信息。显然应通过user_id做join,关联条件为order_info.user_id=user_info.user_id。而用户表的user_id是订单表user_id的超集,即订单记录一定可以在用户表中找到user_id相同的数据,反之则未必。user_info中不满足关联条件的数据,即未下单的用户数据不参与本题统计,应舍弃。综上,内连接即可。
(2)分组聚合
① 统计每日指标应按照下单日期,即order_info中的create_date字段分组。
② 男女下单总金额应对应两个不同的字段,可以通过sum(if(gender = ‘x’, total_amount, 0)实现。该语句的含义是:当性别为’x’时将本条数据的total_amount加到总和上,否则加0,若’x’为男,总和为男性购物总额,女性同理。
③ 最后,decimal类型数据做统计时,保留的小数位数可能会发生改变,计算完成后将类型统一为decimal(16,2)。
3)图解
15. 代码实现
hive>
select create_date,
cast(sum(if(gender = ‘男’, total_amount, 0)) as decimal(16, 2)) total_amount_male,
cast(sum(if(gender = ‘女’, total_amount, 0)) as decimal(16, 2)) total_amount_female from order_info oi
join
user_info ui
on oi.user_id = ui.user_id
group by create_date;

8.2.6 购买过商品1和商品2但是没有购买商品3的顾客

  1. 题目需求
    从订单明细表(order_detail)中查询出所有购买过商品1和商品2,但是没有购买过商品3的用户。
  2. 思路分析
    1)知识储备
    (1)collect_set(col):将col字段的所有值去重后置于一个array类型的对象中。
    (2)collect_list(col):将col字段的所有值置于一个array类型的对象中,不去重。
    (3)array_contains(Array arr, T value):判断数组arr中是否包含value,是则返回true。
    2)实现步骤
    (1)t1子查询
    ① 关联
    这一步要获取用户购买过得所有sku。各sku的下单记录存储在order_detail中,用户和订单的映射关系存储在order_info中,关联两张表获取用户和sku的对应关系。显然,订单明细和订单表通过order_id建立联系,关联条件为order_detail.id=order_info.order_id。订单明细一定有对应的订单表记录,订单表也一定有对应的订单明细记录,因此两张表的所有数据均可满足关联条件,[inner] join、left [outer] join、right [outer] join、full [outer] join的结果都是一样的([]表示可省略)。此处选用[inner] join。
    ② 分组聚合
    统计各用户的指标显然应按照user_id分组。此处要将各用户购买过的所有sku置于同一个数组中,同一用户的不同订单可能包含相同的sku,所以要去重,调用collect_set()方法将sku去重后放入array返回。
    (2)最终结果
    调用array_contains()方法,筛选包含1、2,不包含3的记录,取user_id字段即可。
    3)图解
  3. 代码实现
    hive>
    select user_id
    from (
    select user_id,
    collect_set(sku_id) skus
    from order_detail od
    join
    order_info oi
    on od.order_id = oi.order_id
    group by user_id
    ) t1
    where array_contains(skus, ‘1’)
    and array_contains(skus, ‘2’)
    and !array_contains(skus, ‘3’);

8.2.7 统计每日商品1和商品2销量的差值

  1. 题目需求
    从订单明细表(order_detail)中统计每天商品1和商品2销量(件数)的差值(商品1销量-商品2销量),期望结果如表8-16所示。
    表8-16 每日商品1和商品2的销量差值
    create_date
    diff

2020-10-08 -24
2021-09-27 2
2021-09-30 9
2021-10-01 -10
2021-10-02 -5800
2021-10-03 4
2021-10-04 -55
2021-10-05 -30
2021-10-06 -49
2021-10-07 -40
2022-09-24 0
20. 思路分析
1)知识储备
A IN (val1, val2, …):只要A与括号中任一值相等则返回true。从Hive-0.13开始括号内支持子查询。
2)实现步骤
(1)筛选商品 1和商品2的下单记录
通过in语法筛选sku_id为1或2的数据。
(2)统计销量差
按照create_date分组,通过sum(if(sku_id=‘x’, sku_num, 0))分别统计商品1和商品2的每日销量,作差即可。
3)图解
21. 代码实现
hive>
select create_date,
sum(if(sku_id = ‘1’, sku_num, 0)) - sum(if(sku_id = ‘2’, sku_num, 0)) diff
from order_detail
where sku_id in (‘1’, ‘2’)
group by create_date;

8.2.8 根据商品销售情况进行商品分类

  1. 题目需求
    通过订单详情表(order_detail)的数据,根据销售件数对商品进行分类,销售件数0-5000为冷门商品,5001-19999为一般商品,20000以上为热门商品,统计不同类别商品的数量,期望结果如表8-17所示。
    表8-17 商品销量情况分级统计
    category

(类型) cn

(数量)
一般商品 1
冷门商品 13
热门商品 1
23. 思路分析
1)知识储备
(1)CASE WHEN a THEN b [WHEN c THEN d]* [ELSE e] END:当a为true时返回b,当c为true时返回d,方括号包裹的部分可以有0至多个。所有分支条件都不满足,返回e。
(2)CASE a WHEN b THEN c [WHEN d THEN e]* [ELSE f] END:当a=b时返回c,当a=d时返回e,方括号部分同上,所有分支条件全不满足返回f。
2)执行步骤
(1)t1子查询:获取各sku销量
按照sku_id分组,调用sum()对sku_num求和获得商品累计销量。
(2)t2子查询,根据销量分类
通过case when语法根据累计销量对商品分类,为category字段赋值。
(3)最终结果,统计各类商品数量
按照category分组,统计sku数量。
3)图解
24. 代码实现
hive>
select
t2.category,
count(*) cn
from
(
select
t1.sku_id,
case
when t1.sku_sum >=0 and t1.sku_sum<=5000 then ‘冷门商品’
when t1.sku_sum >=5001 and t1.sku_sum<=19999 then ‘一般商品’
when t1.sku_sum >=20000 then ‘热门商品’
end category
from
(
select
sku_id,
sum(sku_num) sku_sum
from
order_detail
group by
sku_id
)t1
)t2
group by
t2.category;

8.2.9 查询有新增用户的日期的新增用户数和新增用户一日留存率

  1. 题目需求
    从用户登录明细表(user_login_detail)中统计有新增用户的日期的新增用户数(若某日未新增用户,则不出现在统计结果中),并统计这些新增用户的一日留存率。
    用户首次登录为当天新增,次日也登录则为一日留存。一日留存用户占新增用户数的比率为一日留存率。
    期望结果如表8-18所示。
    表8-18 新增用户数和新用户留存统计
    register_date

(注册时间) register_count

(新增用户数) retention_1_rate
<decimal(16,2)>
(留存率)
2021-09-21 2 0.00
2021-09-22 1 0.00
2021-09-23 1 0.00
2021-09-24 1 0.00
2021-09-25 1 0.00
2021-09-26 1 0.00
2021-09-27 1 0.00
2021-10-04 2 0.50
2021-10-06 1 0.00
26. 思路分析
1)知识储备
(1)hive.compat:Hive算数运算符的向后兼容级别,决定了整数相除时返回值的类型。默认值为0.12,int/int返回值类型为double,设置为0.13,int/int返回值类型为decimal。
(2)int与decimal类型做除法运算,返回值类型为decimal。
2)执行步骤
(1)t1子查询
要统计每日新增用户数,首先应获取各用户的注册日期。注册日期的统计思路如下。
读取user_login_detail表,按照user_id分组,调用min()获取首次登录记录,而后转化为yyyy-MM-dd格式的日期字符串即可。
(2)t3子查询
① 获取新增用户的1日留存记录
新增用户的1日留存率依赖于1日留存人数,留存人数的计算需要注册次日的登录记录,这部分数据的筛选思路如下。
注册次日的登录行为应满足如下两个条件:user_id与注册用户id相同,登录日期比注册日期大1。注册记录取自t1,登录数据取自user_login_detail。关联条件如上,满足条件的数据即注册用户的1日留存记录。统计注册人数需要所有的注册记录,因此无论是否满足关联条件,t1表中的数据都应保留。而不满足1日留存条件的登录记录对指标计算无用,舍弃。因此将t1作为主表,通过left join与user_login_detail关联,下文将user_login_detail称为t2表。
② 统计每日的新增用户数和1日留存用户数(留存记录为统计日期次日)
t1表中的user_id数量即注册用户数,如果从t1表中查询,count(*)即注册用户数,但同一用户可能在次日多次登录,①中关联后同一用户可能对应多条记录,即user_id可能重复,因此统计时要去重。按照日期分组,count(distinct t1.user_id)即当日注册用户数。
只有用户在注册次日登录时,关联后取自t2的user_id才不为null,因此非null的t2.user_id个数即留存用户数。但如果用户在次日多次登录,同样可能有重复的t2.user_id,因而要去重。按照注册日期分组,count(distinct t2.user_id)即注册次日的留存用户数。
(3)最终结果
从t3读取数据,注册日期和注册人数可以直接获取,1日留存率可由1日留存人数除以注册人数获得,二者均由count()计算得到,类型为int。上文提到,int之间相除返回值类型为double,而double可能会有精度损失,因而要在计算之前将除数或被除数转换为decimal类型。此外,运算后数据的小数位数可能发生改变,统一转换为decimal(16,2)。
3)图解
27. 代码实现
hive>
select t3.register_date,
t3.register_count,
cast(cast(t3.retention_1_count as decimal(16, 2)) / t3.register_count as decimal(16, 2)) retention_1_rate
from (
select t1.register_date,
count(distinct t1.user_id) register_count,
count(distinct t2.user_id) retention_1_count
from (
select user_id,
date_format(min(login_ts), ‘yyyy-MM-dd’) register_date
from user_login_detail
group by user_id
) t1
left join
user_login_detail t2
on t1.user_id = t2.user_id and datediff(date_format(t2.login_ts, ‘yyyy-MM-dd’), t1.register_date) = 1
group by t1.register_date
) t3;

8.2.10 登录次数及交易次数统计

  1. 题目需求
    分别从登录明细表(user_login_detail)和配送信息表(delivery_info)中的用户登录时间和下单时间字段,统计登陆次数和交易次数,部分结果如表8-19所示。
    表8-19 用户登录次数和交易次数统计
    user_id

(用户id) login_date

(登录时间) login_count

(登陆次数) order_count

(交易次数)
101 2021-09-21 1 0
101 2021-09-27 1 1
101 2021-09-28 1 1
101 2021-09-29 1 1
101 2021-09-30 1 1
1010 2021-09-27 1 0
1010 2021-10-09 1 0
1011 2021-09-21 1 0
1011 2021-10-24 1 0
1011 2022-09-21 1 0
102 2021-09-22 1 0
102 2021-10-01 2 3
102 2021-10-02 1 1
29. 思路分析
1)执行步骤
(1)t1子查询
这一步获取登录次数,数据来源于user_login_detail。分析题干,只有用户在某天登录时才需要统计当日的登录次数,并且登录明细表的一条记录对应一次登录,因此只要按照登录明细的用户Id和登录日期分组,统计各组的数据条数即可。
(2)t2子查询
这一步获取下单次数,数据来源于deliver_info。与t1同理,只须统计有下单记录时的当日下单次数。派送信息表的粒度为一个订单的派送记录,order_id不会重复,因此数据条数等价于订单数。此处按照order_date分组,统计数据条数即可。
(3)最终结果
分析结果集,我们需要将t1和t2通过用户ID和日期字段关联起来。关联后的数据分为三类:满足关联条件的数据为当日登录且下单的记录,应保留。登录未必下单,而下单必须登录,因此t2表的数据必然全部满足关联条件,left join等价于full join。t1表中登录未下单的记录不满足关联条件,但这部分数据也是我们需要的。
综上,关联后的数据分为两类,即满足关联条件的数据和t1中不满足条件的数据,都应保留。将t1作为主表,通过left join或full join关联t2,关联条件为t1.user_id=t2.user_id and t1.login_dt=t2.order_date。获取t1表的user_id,登录日期,登录次数及t2表的订单数字段即可。
t2表的user_id和下单日期字段可能为null,但t1不会,因此关联字段从t1表中获取。此外,order_count字段也可能为null,调用nvl()函数赋默认值。
2)图解
30. 代码实现
hive>
select t1.user_id,
t1.login_dt,
login_count,
nvl(order_count, 0) order_count
from (select user_id,
date_format(login_ts, ‘yyyy-MM-dd’) login_dt,
count(*) login_count
from user_login_detail
group by user_id,
date_format(login_ts, ‘yyyy-MM-dd’)) t1
left join
(select user_id,
order_date,
count(order_id) order_count
from delivery_info
group by user_id, order_date) t2
on t1.user_id = t2.user_id
and t1.login_dt = t2.order_date;

8.2.11 统计每个商品各年度销售总额

  1. 题目需求
    从订单明细表(order_detail)中统计每个商品各年度的销售总额,部分结果如表8-20所示。
    表8-20 商品年度销售总额统计
    sku_id

(商品id) year_date

(年份) sku_sum
<decimal(16,2)>
(销售总额)
1 2020 4000.00
1 2021 98000.00
1 2022 2000.00
10 2020 9400.00
10 2021 20500.00
11 2020 4750.00
11 2021 11250.00
12 2020 1660.00
12 2021 411980.00
13 2021 7300.00
14 2021 17460.00
14 2022 2880.00
15 2020 4500.00
2 2020 260.00
2 2021 60180.00
32. 思路分析
1)执行步骤
数据来源于order_detail,按照sku_id及下单年份分组,统计sku数量和单价的乘积之和即可。年份调用year()处理order_date获得,此外,求和后的decimal类型小数位数可能发生变化,转换为decimal(16,2)类型。
2)图解
33. 代码实现
hive>
select
sku_id,
year(create_date) year_date,
cast(sum(price * sku_num) as decimal(16,2)) sku_sum
from
order_detail
group by
sku_id,
year(create_date);

8.2.12 某周内每件商品每天销售情况

  1. 题目需求
    从订单详情表(order_detail)中查询2021年9月27号-2021年10月3号这一周所有商品每天销售件数,期望结果如表8-21所示。
    表8-21 指定周每天各商品销量统计
    sku_id
    monday
    tuesday
    wednesday
    thursday
    friday
    saturday
    sunday

1 2 0 0 9 8 0 4
10 0 0 0 0 0 48 69
11 0 0 0 0 0 15 61
12 0 0 0 43 0 31 20400
2 0 0 0 0 18 5800 0
3 0 0 0 0 6 0 1
4 0 9 0 0 8 1 5
5 0 33 0 0 0 24 47
6 0 0 0 0 1 5 8
7 0 0 37 0 17 0 20
8 0 0 46 0 48 39 0
9 0 0 12 0 45 0 0
35. 思路分析
1)知识储备
dayofweek(DATE/TIMESTAMP/STRING date):返回当前日期在一周中的序数,Sunday为1,Monday为2,依次类推。返回值类型为int。
2)实现步骤
本题实现思路很简单,首先过滤指定时间范围的数据,然后按照sku_id分组,调用sum(if())函数统计每日的销量即可。需要注意的是dayofweek()函数执行时可能触发Bug,单独调用、与不同的函数结合使用时的结果可能不同,如
(1)select dayofweek(‘2021-09-27’);的返回值为2。
(2)min(if(dayofweek(create_date) = 2, create_date, null))与sum(if(dayofweek(create_date) = 2, sku_num, 0))当create_date取值为’2021-09-27’时dayofweek函数的返回值为1。
(3)collect_set(if(dayofweek(create_date)=2,create_date,null)) 当create_date取值为’2021-09-27’时dayofweek函数的返回值为2,且select语句中包含该字段时,其它字段中dayofweek的调用也会受到影响,同样为2。
综上,dayofweek函数返回值的准确性无法保证,我们通过查询日历的方式认为判断星期。本题中统计的天数较少,这种做法代价不大,当天数很多,需要查询的日期较多时可以查询某一天的数据,而后调用datediff()函数统计指定日期与该日期的天数差,再模以7,根据不同的返回值类型即可判定星期。本题直接查询日历即可。
3)图解
36. 代码实现
hive>
select sku_id,
sum(if(create_date = ‘2021-09-27’, sku_num, 0)) Monday,
sum(if(create_date = ‘2021-09-28’, sku_num, 0)) Tuesday,
sum(if(create_date = ‘2021-09-29’, sku_num, 0)) Wednesday,
sum(if(create_date = ‘2021-09-30’, sku_num, 0)) Thursday,
sum(if(create_date = ‘2021-10-01’, sku_num, 0)) Friday,
sum(if(create_date = ‘2021-10-02’, sku_num, 0)) Saturday,
sum(if(create_date = ‘2021-10-03’, sku_num, 0)) Sunday
from order_detail
where create_date >= ‘2021-09-27’
and create_date <= ‘2021-10-03’
group by sku_id;

8.2.13 同期商品售卖分析表

  1. 题目需求
    从订单明细表(order_detail)中,统计同一个商品在2021年和2022年中同一个月的销量对比,期望结果如表8-22所示。
    表8-22 各商品同期销量对比
    sku_id

(商品id) month

(月份) 2020_skusum

(2020销售量) 2021_skusum

(2021销售量)
1 9 0 11
1 10 2 38
10 10 94 205
11 10 95 225
12 10 83 20556
12 9 0 43
13 1 0 27
14 9 0 12
14 10 0 12
14 1 0 33
15 10 15 0
2 10 26 6018
38. 思路分析
1)知识储备
(1)month(string date):返回date或timestamp类型字符串的month部分,返回值类型为int。
(2)当需要将关键词作为变量名时,应使用飘号``包裹变量,否则报错。
2)执行步骤
首先筛选下单年份在2020或2021的数据,按照sku_id和下单月份分组。组合调用sum(if()),根据create_date所属年份的不同,分别统计2020和2021的累计销量。
3)图解
39. 代码实现
hive> select sku_id,
month(create_date) month,
sum(if(year(create_date) = 2020, sku_num, 0)) 2020_skusum,
sum(if(year(create_date) = 2021, sku_num, 0)) 2021_skusum
from order_detail
where year(create_date) = 2021
or year(create_date) = 2020
group by sku_id, month(create_date);

8.2.14 国庆期间每个sku的收藏量和购买量

  1. 题目需求
    从订单明细表(order_detail)和收藏信息表(favor_info)中统计2021年国庆节期间(10月1日-10月7日),每个商品的购买总数量和总收藏次数,期望结果如表8-23所示。
    表8-23 节日期间各商品下单次数和收藏次数统计
    sku_id
    sku_sum

(购买量) favor_cn

(收藏量)
1 38 1
10 205 2
11 225 2
12 20556 0
2 6018 1
3 30 0
4 44 2
5 209 1
6 26 1
7 180 1
8 148 0
9 182 1
41. 思路分析
1)执行步骤
(1)t1子查询
从order_detail表读取数据,筛选create_date在2021-10-01至2021-10-07之间的数据,按照sku_id分组,对sku_num求和即可。
(2)t2子查询
从favor_info表读取数据,筛选create_date在2021-10-01至2021-10-07之间的数据,按照sku_id分组,统计数据条数即可,该表的user_id字段不存在null值,因此count(user_id)等价于count(*)。
(3)最终结果
这一步需要将t1、t2通过sku_id字段关联在一起,只要用户在国庆期间有下单记录或收藏记录,就应保留当天的数据,因此因使用全外联。两张表的字段都可能为null,因此要用nvl处理。
① t1和t2的sku_id必定不同时为null,nvl(t1.sku_id, t2.sku_id)作为sku_id。
② sku_sum可能为null,赋默认值0即可:nvl(sku_sum, 0)。
③ favor_count可能为null,赋默认值0即可:nvl(favor_count, 0)。
2)图解
42. 代码实现
hive>
select nvl(t1.sku_id, t2.sku_id) sku_id,
nvl(sku_sum, 0) sku_sum,
nvl(favor_count, 0) favor_cn
from (select sku_id,
sum(sku_num) sku_sum
from order_detail
where create_date >= ‘2021-10-01’
and create_date <= ‘2021-10-07’
group by sku_id) t1
full join
(select sku_id,
count(user_id) favor_count
from favor_info
where create_date >= ‘2021-10-01’
and create_date <= ‘2021-10-07’
group by sku_id) t2
on t1.sku_id = t2.sku_id;

8.2.15 国庆节期间各品类商品的7日动销率和滞销率

  1. 题目需求
    动销率的定义为某品类的商品中一段时间内有销量的商品种类数占当前已上架总商品种类数的比例(有销量的商品种类数/已上架总商品种类数)。
    滞销率的定义为某分类商品中一段时间内没有销量的商品种类数占当前已上架总商品种类数的比例(没有销量的商品种类数/已上架总商品种类数)。
    只要当天任一店铺有任何商品的销量就输出该天的统计结果。
    从订单明细表(order_detail)和商品信息表(sku_info)表中统计国庆节期间(10月1日-10月7日)每天每个分类的商品的动销率和滞销率,部分结果如表8-24所示。
    表8-24 国庆期间各分类商品动销率和滞销率
    category_id

(品类id) first_sale_rate
<decimal(16,2)>
(动销) first_unsale_rage
<decimal(16,2)>
(滞销) second_sale_rate
<decimal(16,2)>
(动销) second_unsale_rate
<decimal(16,2)>
(滞销)
1 1.00 0.00 0.50 0.50
2 0.43 0.57 0.43 0.57
3 0.25 0.75 0.75 0.25
44. 思路分析
1)执行步骤
以2021-10-01为例,当日各品类动销率和滞销率之和为1,得到动销率也就得到了滞销率。前者依赖于两个指标:当日各品类有销售记录的sku数量和当日各品类已上架sku数量。销售记录取自order_detail,品类信息和已上架商品信息取自sku_info。实现思路如下。
(1)t1子查询
本题仅须2021年国庆期间的下单记录,因此从order_detail筛选create_date在2021-10-01至2021-10-07的数据即可。
(2)t2子查询
① t1包含了2021年国庆期间所有的商品销售记录,接下来要关联sku_info获得category_id和每日已上架商品信息,显然,关联字段为sku_id。sku_info包含了sku_id的全量数据集,为t1中sku_id的超集,若以t1作为主表,right join等价于full join。统计上架商品数可能需要所有的sku_id,因此sku_info中不满足关联条件的记录应保留,最终以t1为主表通过right join关联sku_info,关联条件为t1.sku_id=sku_info.sku_id。
② 关联后按照category_id分组,指定日期t1表中sku_id的数量为当日有销量的商品数,sku_info表中sku_id的数量为当日已上架商品数,这里要注意,from_date一定不能晚于当日,否则当日未上架不应统计在内。此外,某天sku_id的销售记录可能不止一条,关联后t1表和sku_info表都可能存在重复的sku_id,因此需要去重。统计通过count(distinct if())函数的组合调用实现,当日有销售记录的商品数和当日已上架商品数相除即可获得动销率。上文提到,int类型相除默认返回值类型为double,可能造成精度损失,将分子或分母转换为decimal(16, 2)可以避免这个问题。
(3)最终结果
从t2子查询获取品类信息和2021年国庆期间每日的动销率,用1减去动销率即为滞销率。最后调用cast()函数将二者统一为decimal(16, 2)。
2)图解

t2统计获得的动销率小数位数过多不便展示,且各天动销率计算逻辑类似,下图精简。

  1. 代码实现
    hive>
    select category_id,
    cast(first_sale_rate as decimal(16, 2)) first_sale_rate,
    cast((1 - first_sale_rate) as decimal(16, 2)) first_unsale_rate,
    cast(second_sale_rate as decimal(16, 2)) second_sale_rate,
    cast((1 - second_sale_rate) as decimal(16, 2)) second_unsale_rate,
    cast(third_sale_rate as decimal(16, 2)) third_sale_rate,
    cast((1 - third_sale_rate) as decimal(16, 2)) third_unsale_rate,
    cast(fourth_sale_rate as decimal(16, 2)) fourth_sale_rate,
    cast((1 - fourth_sale_rate) as decimal(16, 2)) fourth_unsale_rate,
    cast(fifth_sale_rate as decimal(16, 2)) fifth_sale_rate,
    cast((1 - fifth_sale_rate) as decimal(16, 2)) fifth_unsale_rate,
    cast(sixth_sale_rate as decimal(16, 2)) sixth_sale_rate,
    cast((1 - sixth_sale_rate) as decimal(16, 2)) sixth_unsale_rate,
    cast(seventh_sale_rate as decimal(16, 2)) seventh_sale_rate,
    cast((1 - seventh_sale_rate) as decimal(16, 2)) seventh_unsale_rate
    from (select category_id,
    cast(count(distinct if(t1.create_date = ‘2021-10-01’, t1.sku_id, null)) as decimal(16, 2)) /
    count(distinct if(sku_info.from_date <= ‘2021-10-01’, sku_info.sku_id, null)) first_sale_rate,
    cast(count(distinct if(t1.create_date = ‘2021-10-02’, t1.sku_id, null)) as decimal(16, 2)) /
    count(distinct if(sku_info.from_date <= ‘2021-10-02’, sku_info.sku_id, null)) second_sale_rate,
    cast(count(distinct if(t1.create_date = ‘2021-10-03’, t1.sku_id, null)) as decimal(16, 2)) /
    count(distinct if(sku_info.from_date <= ‘2021-10-03’, sku_info.sku_id, null)) third_sale_rate,
    cast(count(distinct if(t1.create_date = ‘2021-10-04’, t1.sku_id, null)) as decimal(16, 2)) /
    count(distinct if(sku_info.from_date <= ‘2021-10-04’, sku_info.sku_id, null)) fourth_sale_rate,
    cast(count(distinct if(t1.create_date = ‘2021-10-05’, t1.sku_id, null)) as decimal(16, 2)) /
    count(distinct if(sku_info.from_date <= ‘2021-10-05’, sku_info.sku_id, null)) fifth_sale_rate,
    cast(count(distinct if(t1.create_date = ‘2021-10-06’, t1.sku_id, null)) as decimal(16, 2)) /
    count(distinct if(sku_info.from_date <= ‘2021-10-06’, sku_info.sku_id, null)) sixth_sale_rate,
    cast(count(distinct if(t1.create_date = ‘2021-10-07’, t1.sku_id, null)) as decimal(16, 2)) /
    count(distinct if(sku_info.from_date <= ‘2021-10-07’, sku_info.sku_id, null)) seventh_sale_rate
    from (select sku_id,
    create_date
    from order_detail
    where create_date >= ‘2021-10-01’
    and create_date <= ‘2021-10-07’) t1
    right join sku_info
    on t1.sku_id = sku_info.sku_id
    group by category_id) t2;

8.3 本章总结

本章主要考察函数和实际业务场景的连用,难度适中,多多思考数据之间的关系,多多思考自己的结果中需要什么数据,想清楚后才能完成练习。

10.综合案例练习之中级SQL

1.环境准备

1.1 用户信息表

1)表结构
user_id(用户id) Gender(性别) Birthday(生日)
101 男 1990-01-01
102 女 1991-02-01
103 女 1992-03-01
104 男 1993-04-01
2)建表语句
hive>
DROP TABLE IF EXISTS user_info;
create table user_info(
user_id string COMMENT ‘用户id’,
gender string COMMENT ‘性别’,
birthday string COMMENT ‘生日’
) COMMENT ‘用户信息表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
3)数据装载
hive>
insert overwrite table user_info
values (‘101’, ‘男’, ‘1990-01-01’),
(‘102’, ‘女’, ‘1991-02-01’),
(‘103’, ‘女’, ‘1992-03-01’),
(‘104’, ‘男’, ‘1993-04-01’),
(‘105’, ‘女’, ‘1994-05-01’),
(‘106’, ‘男’, ‘1995-06-01’),
(‘107’, ‘女’, ‘1996-07-01’),
(‘108’, ‘男’, ‘1997-08-01’),
(‘109’, ‘女’, ‘1998-09-01’),
(‘1010’, ‘男’, ‘1999-10-01’);

1.2 商品信息表

1)表结构
sku_id
(商品id) name
(商品名称) category_id
(分类id) from_date
(上架日期) price
(商品价格)
1 xiaomi 10 1 2020-01-01 2000
6 洗碗机 2 2020-02-01 2000
9 自行车 3 2020-01-01 1000
2)建表语句
hive>
DROP TABLE IF EXISTS sku_info;
CREATE TABLE sku_info(
sku_id string COMMENT ‘商品id’,
name string COMMENT ‘商品名称’,
category_id string COMMENT ‘所属分类id’,
from_date string COMMENT ‘上架日期’,
price double COMMENT ‘商品单价’
) COMMENT ‘商品属性表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
3)数据装载
hive>
insert overwrite table sku_info
values (‘1’, ‘xiaomi 10’, ‘1’, ‘2020-01-01’, 2000),
(‘2’, ‘手机壳’, ‘1’, ‘2020-02-01’, 10),
(‘3’, ‘apple 12’, ‘1’, ‘2020-03-01’, 5000),
(‘4’, ‘xiaomi 13’, ‘1’, ‘2020-04-01’, 6000),
(‘5’, ‘破壁机’, ‘2’, ‘2020-01-01’, 500),
(‘6’, ‘洗碗机’, ‘2’, ‘2020-02-01’, 2000),
(‘7’, ‘热水壶’, ‘2’, ‘2020-03-01’, 100),
(‘8’, ‘微波炉’, ‘2’, ‘2020-04-01’, 600),
(‘9’, ‘自行车’, ‘3’, ‘2020-01-01’, 1000),
(‘10’, ‘帐篷’, ‘3’, ‘2020-02-01’, 100),
(‘11’, ‘烧烤架’, ‘3’, ‘2020-02-01’, 50),
(‘12’, ‘遮阳伞’, ‘3’, ‘2020-03-01’, 20);

1.3 商品分类信息表

1)表结构
category_id(分类id) category_name(分类名称)
1 数码
2 厨卫
3 户外
2)建表语句
hive>
DROP TABLE IF EXISTS category_info;
create table category_info(
category_id string,
category_name string
) COMMENT ‘品类表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
3)数据装载
hive>
insert overwrite table category_info
values (‘1’,‘数码’),
(‘2’,‘厨卫’),
(‘3’,‘户外’);

1.4 订单信息表

1)表结构
order_id
(订单id) user_id
(用户id) create_date
(下单日期) total_amount
(订单金额)
1 101 2021-09-30 29000.00
10 103 2020-10-02 28000.00
2)建表语句
hive>
DROP TABLE IF EXISTS order_info;
create table order_info(
order_id string COMMENT ‘订单id’,
user_id string COMMENT ‘用户id’,
create_date string COMMENT ‘下单日期’,
total_amount decimal(16, 2) COMMENT ‘订单总金额’
) COMMENT ‘订单表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
3)数据装载
hive>
insert overwrite table order_info
values (‘1’, ‘101’, ‘2021-09-27’, 29000.00),
(‘2’, ‘101’, ‘2021-09-28’, 70500.00),
(‘3’, ‘101’, ‘2021-09-29’, 43300.00),
(‘4’, ‘101’, ‘2021-09-30’, 860.00),
(‘5’, ‘102’, ‘2021-10-01’, 46180.00),
(‘6’, ‘102’, ‘2021-10-01’, 50000.00),
(‘7’, ‘102’, ‘2021-10-01’, 75500.00),
(‘8’, ‘102’, ‘2021-10-02’, 6170.00),
(‘9’, ‘103’, ‘2021-10-02’, 18580.00),
(‘10’, ‘103’, ‘2021-10-02’, 28000.00),
(‘11’, ‘103’, ‘2021-10-02’, 23400.00),
(‘12’, ‘103’, ‘2021-10-03’, 5910.00),
(‘13’, ‘104’, ‘2021-10-03’, 13000.00),
(‘14’, ‘104’, ‘2021-10-03’, 69500.00),
(‘15’, ‘104’, ‘2021-10-03’, 2000.00),
(‘16’, ‘104’, ‘2021-10-03’, 5380.00),
(‘17’, ‘105’, ‘2021-10-04’, 6210.00),
(‘18’, ‘105’, ‘2021-10-04’, 68000.00),
(‘19’, ‘105’, ‘2021-10-04’, 43100.00),
(‘20’, ‘105’, ‘2021-10-04’, 2790.00),
(‘21’, ‘106’, ‘2021-10-04’, 9390.00),
(‘22’, ‘106’, ‘2021-10-05’, 58000.00),
(‘23’, ‘106’, ‘2021-10-05’, 46600.00),
(‘24’, ‘106’, ‘2021-10-05’, 5160.00),
(‘25’, ‘107’, ‘2021-10-05’, 55350.00),
(‘26’, ‘107’, ‘2021-10-05’, 14500.00),
(‘27’, ‘107’, ‘2021-10-06’, 47400.00),
(‘28’, ‘107’, ‘2021-10-06’, 6900.00),
(‘29’, ‘108’, ‘2021-10-06’, 56570.00),
(‘30’, ‘108’, ‘2021-10-06’, 44500.00),
(‘31’, ‘108’, ‘2021-10-07’, 50800.00),
(‘32’, ‘108’, ‘2021-10-07’, 3900.00),
(‘33’, ‘109’, ‘2021-10-07’, 41480.00),
(‘34’, ‘109’, ‘2021-10-07’, 88000.00),
(‘35’, ‘109’, ‘2020-10-08’, 15000.00),
(‘36’, ‘109’, ‘2020-10-08’, 9020.00),
(‘37’, ‘1010’, ‘2020-10-08’, 9260.00),
(‘38’, ‘1010’, ‘2020-10-08’, 12000.00),
(‘39’, ‘1010’, ‘2020-10-08’, 23900.00),
(‘40’, ‘1010’, ‘2020-10-08’, 6790.00);

1.5 订单明细表

1)表结构
order_detail_id
(订单明细id) order_id
(订单id) sku_id
(商品id) create_date
(下单日期) price
(商品单价) sku_num
(商品件数)
1 1 1 2021-09-30 2000.00 2
2 1 3 2021-09-30 5000.00 5
22 10 4 2020-10-02 6000.00 1
23 10 5 2020-10-02 500.00 24
24 10 6 2020-10-02 2000.00 5
2)建表语句
hive>
DROP TABLE IF EXISTS order_detail;
CREATE TABLE order_detail
(
order_detail_id string COMMENT ‘订单明细id’,
order_id string COMMENT ‘订单id’,
sku_id string COMMENT ‘商品id’,
create_date string COMMENT ‘下单日期’,
price decimal(16, 2) COMMENT ‘下单时的商品单价’,
sku_num int COMMENT ‘下单商品件数’
) COMMENT ‘订单明细表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
3)数据装载
hive>
INSERT overwrite table order_detail
values (‘1’, ‘1’, ‘1’, ‘2021-09-27’, 2000.00, 2),
(‘2’, ‘1’, ‘3’, ‘2021-09-27’, 5000.00, 5),
(‘3’, ‘2’, ‘4’, ‘2021-09-28’, 6000.00, 9),
(‘4’, ‘2’, ‘5’, ‘2021-09-28’, 500.00, 33),
(‘5’, ‘3’, ‘7’, ‘2021-09-29’, 100.00, 37),
(‘6’, ‘3’, ‘8’, ‘2021-09-29’, 600.00, 46),
(‘7’, ‘3’, ‘9’, ‘2021-09-29’, 1000.00, 12),
(‘8’, ‘4’, ‘12’, ‘2021-09-30’, 20.00, 43),
(‘9’, ‘5’, ‘1’, ‘2021-10-01’, 2000.00, 8),
(‘10’, ‘5’, ‘2’, ‘2021-10-01’, 10.00, 18),
(‘11’, ‘5’, ‘3’, ‘2021-10-01’, 5000.00, 6),
(‘12’, ‘6’, ‘4’, ‘2021-10-01’, 6000.00, 8),
(‘13’, ‘6’, ‘6’, ‘2021-10-01’, 2000.00, 1),
(‘14’, ‘7’, ‘7’, ‘2021-10-01’, 100.00, 17),
(‘15’, ‘7’, ‘8’, ‘2021-10-01’, 600.00, 48),
(‘16’, ‘7’, ‘9’, ‘2021-10-01’, 1000.00, 45),
(‘17’, ‘8’, ‘10’, ‘2021-10-02’, 100.00, 48),
(‘18’, ‘8’, ‘11’, ‘2021-10-02’, 50.00, 15),
(‘19’, ‘8’, ‘12’, ‘2021-10-02’, 20.00, 31),
(‘20’, ‘9’, ‘1’, ‘2021-09-30’, 2000.00, 9),
(‘21’, ‘9’, ‘2’, ‘2021-10-02’, 10.00, 5800),
(‘22’, ‘10’, ‘4’, ‘2021-10-02’, 6000.00, 1),
(‘23’, ‘10’, ‘5’, ‘2021-10-02’, 500.00, 24),
(‘24’, ‘10’, ‘6’, ‘2021-10-02’, 2000.00, 5),
(‘25’, ‘11’, ‘8’, ‘2021-10-02’, 600.00, 39),
(‘26’, ‘12’, ‘10’, ‘2021-10-03’, 100.00, 47),
(‘27’, ‘12’, ‘11’, ‘2021-10-03’, 50.00, 19),
(‘28’, ‘12’, ‘12’, ‘2021-10-03’, 20.00, 13000),
(‘29’, ‘13’, ‘1’, ‘2021-10-03’, 2000.00, 4),
(‘30’, ‘13’, ‘3’, ‘2021-10-03’, 5000.00, 1),
(‘31’, ‘14’, ‘4’, ‘2021-10-03’, 6000.00, 5),
(‘32’, ‘14’, ‘5’, ‘2021-10-03’, 500.00, 47),
(‘33’, ‘14’, ‘6’, ‘2021-10-03’, 2000.00, 8),
(‘34’, ‘15’, ‘7’, ‘2021-10-03’, 100.00, 20),
(‘35’, ‘16’, ‘10’, ‘2021-10-03’, 100.00, 22),
(‘36’, ‘16’, ‘11’, ‘2021-10-03’, 50.00, 42),
(‘37’, ‘16’, ‘12’, ‘2021-10-03’, 20.00, 7400),
(‘38’, ‘17’, ‘1’, ‘2021-10-04’, 2000.00, 3),
(‘39’, ‘17’, ‘2’, ‘2021-10-04’, 10.00, 21),
(‘40’, ‘18’, ‘4’, ‘2021-10-04’, 6000.00, 8),
(‘41’, ‘18’, ‘5’, ‘2021-10-04’, 500.00, 28),
(‘42’, ‘18’, ‘6’, ‘2021-10-04’, 2000.00, 3),
(‘43’, ‘19’, ‘7’, ‘2021-10-04’, 100.00, 55),
(‘44’, ‘19’, ‘8’, ‘2021-10-04’, 600.00, 11),
(‘45’, ‘19’, ‘9’, ‘2021-10-04’, 1000.00, 31),
(‘46’, ‘20’, ‘11’, ‘2021-10-04’, 50.00, 45),
(‘47’, ‘20’, ‘12’, ‘2021-10-04’, 20.00, 27),
(‘48’, ‘21’, ‘1’, ‘2021-10-04’, 2000.00, 2),
(‘49’, ‘21’, ‘2’, ‘2021-10-04’, 10.00, 39),
(‘50’, ‘21’, ‘3’, ‘2021-10-04’, 5000.00, 1),
(‘51’, ‘22’, ‘4’, ‘2021-10-05’, 6000.00, 8),
(‘52’, ‘22’, ‘5’, ‘2021-10-05’, 500.00, 20),
(‘53’, ‘23’, ‘7’, ‘2021-10-05’, 100.00, 58),
(‘54’, ‘23’, ‘8’, ‘2021-10-05’, 600.00, 18),
(‘55’, ‘23’, ‘9’, ‘2021-10-05’, 1000.00, 30),
(‘56’, ‘24’, ‘10’, ‘2021-10-05’, 100.00, 27),
(‘57’, ‘24’, ‘11’, ‘2021-10-05’, 50.00, 28),
(‘58’, ‘24’, ‘12’, ‘2021-10-05’, 20.00, 53),
(‘59’, ‘25’, ‘1’, ‘2021-10-05’, 2000.00, 5),
(‘60’, ‘25’, ‘2’, ‘2021-10-05’, 10.00, 35),
(‘61’, ‘25’, ‘3’, ‘2021-10-05’, 5000.00, 9),
(‘62’, ‘26’, ‘4’, ‘2021-10-05’, 6000.00, 1),
(‘63’, ‘26’, ‘5’, ‘2021-10-05’, 500.00, 13),
(‘64’, ‘26’, ‘6’, ‘2021-10-05’, 2000.00, 1),
(‘65’, ‘27’, ‘7’, ‘2021-10-06’, 100.00, 30),
(‘66’, ‘27’, ‘8’, ‘2021-10-06’, 600.00, 19),
(‘67’, ‘27’, ‘9’, ‘2021-10-06’, 1000.00, 33),
(‘68’, ‘28’, ‘10’, ‘2021-10-06’, 100.00, 37),
(‘69’, ‘28’, ‘11’, ‘2021-10-06’, 50.00, 46),
(‘70’, ‘28’, ‘12’, ‘2021-10-06’, 20.00, 45),
(‘71’, ‘29’, ‘1’, ‘2021-10-06’, 2000.00, 8),
(‘72’, ‘29’, ‘2’, ‘2021-10-06’, 10.00, 57),
(‘73’, ‘29’, ‘3’, ‘2021-10-06’, 5000.00, 8),
(‘74’, ‘30’, ‘4’, ‘2021-10-06’, 6000.00, 3),
(‘75’, ‘30’, ‘5’, ‘2021-10-06’, 500.00, 33),
(‘76’, ‘30’, ‘6’, ‘2021-10-06’, 2000.00, 5),
(‘77’, ‘31’, ‘8’, ‘2021-10-07’, 600.00, 13),
(‘78’, ‘31’, ‘9’, ‘2021-10-07’, 1000.00, 43),
(‘79’, ‘32’, ‘10’, ‘2021-10-07’, 100.00, 24),
(‘80’, ‘32’, ‘11’, ‘2021-10-07’, 50.00, 30),
(‘81’, ‘33’, ‘1’, ‘2021-10-07’, 2000.00, 8),
(‘82’, ‘33’, ‘2’, ‘2021-10-07’, 10.00, 48),
(‘83’, ‘33’, ‘3’, ‘2021-10-07’, 5000.00, 5),
(‘84’, ‘34’, ‘4’, ‘2021-10-07’, 6000.00, 10),
(‘85’, ‘34’, ‘5’, ‘2021-10-07’, 500.00, 44),
(‘86’, ‘34’, ‘6’, ‘2021-10-07’, 2000.00, 3),
(‘87’, ‘35’, ‘8’, ‘2020-10-08’, 600.00, 25),
(‘88’, ‘36’, ‘10’, ‘2020-10-08’, 100.00, 57),
(‘89’, ‘36’, ‘11’, ‘2020-10-08’, 50.00, 44),
(‘90’, ‘36’, ‘12’, ‘2020-10-08’, 20.00, 56),
(‘91’, ‘37’, ‘1’, ‘2020-10-08’, 2000.00, 2),
(‘92’, ‘37’, ‘2’, ‘2020-10-08’, 10.00, 26),
(‘93’, ‘37’, ‘3’, ‘2020-10-08’, 5000.00, 1),
(‘94’, ‘38’, ‘6’, ‘2020-10-08’, 2000.00, 6),
(‘95’, ‘39’, ‘7’, ‘2020-10-08’, 100.00, 35),
(‘96’, ‘39’, ‘8’, ‘2020-10-08’, 600.00, 34),
(‘97’, ‘40’, ‘10’, ‘2020-10-08’, 100.00, 37),
(‘98’, ‘40’, ‘11’, ‘2020-10-08’, 50.00, 51),
(‘99’, ‘40’, ‘12’, ‘2020-10-08’, 20.00, 27);

1.6 登录明细表

1)表结构
user_id(用户id) ip_address(ip地址) login_ts(登录时间) logout_ts(登出时间)
101 180.149.130.161 2021-09-21 08:00:00 2021-09-27 08:30:00
102 120.245.11.2 2021-09-22 09:00:00 2021-09-27 09:30:00
103 27.184.97.3 2021-09-23 10:00:00 2021-09-27 10:30:00
2)建表语句
hive>
DROP TABLE IF EXISTS user_login_detail;
CREATE TABLE user_login_detail
(
user_id string comment ‘用户id’,
ip_address string comment ‘ip地址’,
login_ts string comment ‘登录时间’,
logout_ts string comment ‘登出时间’
) COMMENT ‘用户登录明细表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
3)数据装载
hive>
INSERT overwrite table user_login_detail
VALUES (‘101’, ‘180.149.130.161’, ‘2021-09-21 08:00:00’, ‘2021-09-27 08:30:00’),
(‘101’, ‘180.149.130.161’, ‘2021-09-27 08:00:00’, ‘2021-09-27 08:30:00’),
(‘101’, ‘180.149.130.161’, ‘2021-09-28 09:00:00’, ‘2021-09-28 09:10:00’),
(‘101’, ‘180.149.130.161’, ‘2021-09-29 13:30:00’, ‘2021-09-29 13:50:00’),
(‘101’, ‘180.149.130.161’, ‘2021-09-30 20:00:00’, ‘2021-09-30 20:10:00’),
(‘102’, ‘120.245.11.2’, ‘2021-09-22 09:00:00’, ‘2021-09-27 09:30:00’),
(‘102’, ‘120.245.11.2’, ‘2021-10-01 08:00:00’, ‘2021-10-01 08:30:00’),
(‘102’, ‘180.149.130.174’, ‘2021-10-01 07:50:00’, ‘2021-10-01 08:20:00’),
(‘102’, ‘120.245.11.2’, ‘2021-10-02 08:00:00’, ‘2021-10-02 08:30:00’),
(‘103’, ‘27.184.97.3’, ‘2021-09-23 10:00:00’, ‘2021-09-27 10:30:00’),
(‘103’, ‘27.184.97.3’, ‘2021-10-03 07:50:00’, ‘2021-10-03 09:20:00’),
(‘104’, ‘27.184.97.34’, ‘2021-09-24 11:00:00’, ‘2021-09-27 11:30:00’),
(‘104’, ‘27.184.97.34’, ‘2021-10-03 07:50:00’, ‘2021-10-03 08:20:00’),
(‘104’, ‘27.184.97.34’, ‘2021-10-03 08:50:00’, ‘2021-10-03 10:20:00’),
(‘104’, ‘120.245.11.89’, ‘2021-10-03 08:40:00’, ‘2021-10-03 10:30:00’),
(‘105’, ‘119.180.192.212’, ‘2021-10-04 09:10:00’, ‘2021-10-04 09:30:00’),
(‘106’, ‘119.180.192.66’, ‘2021-10-04 08:40:00’, ‘2021-10-04 10:30:00’),
(‘106’, ‘119.180.192.66’, ‘2021-10-05 21:50:00’, ‘2021-10-05 22:40:00’),
(‘107’, ‘219.134.104.7’, ‘2021-09-25 12:00:00’, ‘2021-09-27 12:30:00’),
(‘107’, ‘219.134.104.7’, ‘2021-10-05 22:00:00’, ‘2021-10-05 23:00:00’),
(‘107’, ‘219.134.104.7’, ‘2021-10-06 09:10:00’, ‘2021-10-06 10:20:00’),
(‘107’, ‘27.184.97.46’, ‘2021-10-06 09:00:00’, ‘2021-10-06 10:00:00’),
(‘108’, ‘101.227.131.22’, ‘2021-10-06 09:00:00’, ‘2021-10-06 10:00:00’),
(‘108’, ‘101.227.131.22’, ‘2021-10-06 22:00:00’, ‘2021-10-06 23:00:00’),
(‘109’, ‘101.227.131.29’, ‘2021-09-26 13:00:00’, ‘2021-09-27 13:30:00’),
(‘109’, ‘101.227.131.29’, ‘2021-10-06 08:50:00’, ‘2021-10-06 10:20:00’),
(‘109’, ‘101.227.131.29’, ‘2021-10-08 09:00:00’, ‘2021-10-08 09:10:00’),
(‘1010’, ‘119.180.192.10’, ‘2021-09-27 14:00:00’, ‘2021-09-27 14:30:00’),
(‘1010’, ‘119.180.192.10’, ‘2021-10-09 08:50:00’, ‘2021-10-09 10:20:00’);

1.7 商品价格变更明细表

1)表结构
sku_id(商品id) new_price(本次变更之后的价格) change_date(变更日期)
1 1900.00 2021-09-25
1 2000.00 2021-09-26
2 80.00 2021-09-29
2 10.00 2021-09-30
2)建表语句
hive>
DROP TABLE IF EXISTS sku_price_modify_detail;
CREATE TABLE sku_price_modify_detail
(
sku_id string comment ‘商品id’,
new_price decimal(16, 2) comment ‘更改后的价格’,
change_date string comment ‘变动日期’
) COMMENT ‘商品价格变更明细表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
3)数据装载
hive>
insert overwrite table sku_price_modify_detail
values (‘1’, 1900, ‘2021-09-25’),
(‘1’, 2000, ‘2021-09-26’),
(‘2’, 80, ‘2021-09-29’),
(‘2’, 10, ‘2021-09-30’),
(‘3’, 4999, ‘2021-09-25’),
(‘3’, 5000, ‘2021-09-26’),
(‘4’, 5600, ‘2021-09-26’),
(‘4’, 6000, ‘2021-09-27’),
(‘5’, 490, ‘2021-09-27’),
(‘5’, 500, ‘2021-09-28’),
(‘6’, 1988, ‘2021-09-30’),
(‘6’, 2000, ‘2021-10-01’),
(‘7’, 88, ‘2021-09-28’),
(‘7’, 100, ‘2021-09-29’),
(‘8’, 800, ‘2021-09-28’),
(‘8’, 600, ‘2021-09-29’),
(‘9’, 1100, ‘2021-09-27’),
(‘9’, 1000, ‘2021-09-28’),
(‘10’, 90, ‘2021-10-01’),
(‘10’, 100, ‘2021-10-02’),
(‘11’, 66, ‘2021-10-01’),
(‘11’, 50, ‘2021-10-02’),
(‘12’, 35, ‘2021-09-28’),
(‘12’, 20, ‘2021-09-29’);

1.8 配送信息表

1)表结构
delivery_id
(运单id) order_id
(订单id) user_id
(用户id) order_date
(下单日期) custom_date
(期望配送日期)
1 1 101 2021-09-27 2021-09-29
2 2 101 2021-09-28 2021-09-28
3 3 101 2021-09-29 2021-09-30
2)建表语句
hive>
DROP TABLE IF EXISTS delivery_info;
CREATE TABLE delivery_info
(
delivery_id string comment ‘配送单id’,
order_id string comment ‘订单id’,
user_id string comment ‘用户id’,
order_date string comment ‘下单日期’,
custom_date string comment ‘期望配送日期’
) COMMENT ‘邮寄信息表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
3)数据装载
hive>
insert overwrite table delivery_info
values (‘1’, ‘1’, ‘101’, ‘2021-09-27’, ‘2021-09-29’),
(‘2’, ‘2’, ‘101’, ‘2021-09-28’, ‘2021-09-28’),
(‘3’, ‘3’, ‘101’, ‘2021-09-29’, ‘2021-09-30’),
(‘4’, ‘4’, ‘101’, ‘2021-09-30’, ‘2021-10-01’),
(‘5’, ‘5’, ‘102’, ‘2021-10-01’, ‘2021-10-01’),
(‘6’, ‘6’, ‘102’, ‘2021-10-01’, ‘2021-10-01’),
(‘7’, ‘7’, ‘102’, ‘2021-10-01’, ‘2021-10-03’),
(‘8’, ‘8’, ‘102’, ‘2021-10-02’, ‘2021-10-02’),
(‘9’, ‘9’, ‘103’, ‘2021-10-02’, ‘2021-10-03’),
(‘10’, ‘10’, ‘103’, ‘2021-10-02’, ‘2021-10-04’),
(‘11’, ‘11’, ‘103’, ‘2021-10-02’, ‘2021-10-02’),
(‘12’, ‘12’, ‘103’, ‘2021-10-03’, ‘2021-10-03’),
(‘13’, ‘13’, ‘104’, ‘2021-10-03’, ‘2021-10-04’),
(‘14’, ‘14’, ‘104’, ‘2021-10-03’, ‘2021-10-04’),
(‘15’, ‘15’, ‘104’, ‘2021-10-03’, ‘2021-10-03’),
(‘16’, ‘16’, ‘104’, ‘2021-10-03’, ‘2021-10-03’),
(‘17’, ‘17’, ‘105’, ‘2021-10-04’, ‘2021-10-04’),
(‘18’, ‘18’, ‘105’, ‘2021-10-04’, ‘2021-10-06’),
(‘19’, ‘19’, ‘105’, ‘2021-10-04’, ‘2021-10-06’),
(‘20’, ‘20’, ‘105’, ‘2021-10-04’, ‘2021-10-04’),
(‘21’, ‘21’, ‘106’, ‘2021-10-04’, ‘2021-10-04’),
(‘22’, ‘22’, ‘106’, ‘2021-10-05’, ‘2021-10-05’),
(‘23’, ‘23’, ‘106’, ‘2021-10-05’, ‘2021-10-05’),
(‘24’, ‘24’, ‘106’, ‘2021-10-05’, ‘2021-10-07’),
(‘25’, ‘25’, ‘107’, ‘2021-10-05’, ‘2021-10-05’),
(‘26’, ‘26’, ‘107’, ‘2021-10-05’, ‘2021-10-06’),
(‘27’, ‘27’, ‘107’, ‘2021-10-06’, ‘2021-10-06’),
(‘28’, ‘28’, ‘107’, ‘2021-10-06’, ‘2021-10-07’),
(‘29’, ‘29’, ‘108’, ‘2021-10-06’, ‘2021-10-06’),
(‘30’, ‘30’, ‘108’, ‘2021-10-06’, ‘2021-10-06’),
(‘31’, ‘31’, ‘108’, ‘2021-10-07’, ‘2021-10-09’),
(‘32’, ‘32’, ‘108’, ‘2021-10-07’, ‘2021-10-09’),
(‘33’, ‘33’, ‘109’, ‘2021-10-07’, ‘2021-10-08’),
(‘34’, ‘34’, ‘109’, ‘2021-10-07’, ‘2021-10-08’),
(‘35’, ‘35’, ‘109’, ‘2021-10-08’, ‘2021-10-10’),
(‘36’, ‘36’, ‘109’, ‘2021-10-08’, ‘2021-10-09’),
(‘37’, ‘37’, ‘1010’, ‘2021-10-08’, ‘2021-10-10’),
(‘38’, ‘38’, ‘1010’, ‘2021-10-08’, ‘2021-10-10’),
(‘39’, ‘39’, ‘1010’, ‘2021-10-08’, ‘2021-10-09’),
(‘40’, ‘40’, ‘1010’, ‘2021-10-08’, ‘2021-10-09’);

1.9 好友关系表

1)表结构
user1_id(用户1 id) user2_id(用户2 id)
101 1010
101 108
101 106
注:表中一行数据中的两个user_id,表示两个用户互为好友。
2)建表语句
hive>
DROP TABLE IF EXISTS friendship_info;
CREATE TABLE friendship_info(
user1_id string comment ‘用户1id’,
user2_id string comment ‘用户2id’
) COMMENT ‘用户关系表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
3)数据装载
hive>
insert overwrite table friendship_info
values (‘101’, ‘1010’),
(‘101’, ‘108’),
(‘101’, ‘106’),
(‘101’, ‘104’),
(‘101’, ‘102’),
(‘102’, ‘1010’),
(‘102’, ‘108’),
(‘102’, ‘106’),
(‘102’, ‘104’),
(‘102’, ‘102’),
(‘103’, ‘1010’),
(‘103’, ‘108’),
(‘103’, ‘106’),
(‘103’, ‘104’),
(‘103’, ‘102’),
(‘104’, ‘1010’),
(‘104’, ‘108’),
(‘104’, ‘106’),
(‘104’, ‘104’),
(‘104’, ‘102’),
(‘105’, ‘1010’),
(‘105’, ‘108’),
(‘105’, ‘106’),
(‘105’, ‘104’),
(‘105’, ‘102’),
(‘106’, ‘1010’),
(‘106’, ‘108’),
(‘106’, ‘106’),
(‘106’, ‘104’),
(‘106’, ‘102’),
(‘107’, ‘1010’),
(‘107’, ‘108’),
(‘107’, ‘106’),
(‘107’, ‘104’),
(‘107’, ‘102’),
(‘108’, ‘1010’),
(‘108’, ‘108’),
(‘108’, ‘106’),
(‘108’, ‘104’),
(‘108’, ‘102’),
(‘109’, ‘1010’),
(‘109’, ‘108’),
(‘109’, ‘106’),
(‘109’, ‘104’),
(‘109’, ‘102’),
(‘1010’, ‘1010’),
(‘1010’, ‘108’),
(‘1010’, ‘106’),
(‘1010’, ‘104’),
(‘1010’, ‘102’);
1.10 收藏信息表
1)表结构
user_id(用户id) sku_id(商品id) create_date(收藏日期)
101 3 2021-09-23
101 12 2021-09-23
101 6 2021-09-25
2)建表语句
hive>
DROP TABLE IF EXISTS favor_info;
CREATE TABLE favor_info
(
user_id string comment ‘用户id’,
sku_id string comment ‘商品id’,
create_date string comment ‘收藏日期’
) COMMENT ‘用户收藏表’
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\t’;
3)数据装载
hive>
insert overwrite table favor_info
values (‘101’, ‘3’, ‘2021-09-23’),
(‘101’, ‘12’, ‘2021-09-23’),
(‘101’, ‘6’, ‘2021-09-25’),
(‘101’, ‘10’, ‘2021-09-21’),
(‘101’, ‘5’, ‘2021-09-25’),
(‘102’, ‘1’, ‘2021-09-24’),
(‘102’, ‘2’, ‘2021-09-24’),
(‘102’, ‘8’, ‘2021-09-23’),
(‘102’, ‘12’, ‘2021-09-22’),
(‘102’, ‘11’, ‘2021-09-23’),
(‘102’, ‘9’, ‘2021-09-25’),
(‘102’, ‘4’, ‘2021-09-25’),
(‘102’, ‘6’, ‘2021-09-23’),
(‘102’, ‘7’, ‘2021-09-26’),
(‘103’, ‘8’, ‘2021-09-24’),
(‘103’, ‘5’, ‘2021-09-25’),
(‘103’, ‘6’, ‘2021-09-26’),
(‘103’, ‘12’, ‘2021-09-27’),
(‘103’, ‘7’, ‘2021-09-25’),
(‘103’, ‘10’, ‘2021-09-25’),
(‘103’, ‘4’, ‘2021-09-24’),
(‘103’, ‘11’, ‘2021-09-25’),
(‘103’, ‘3’, ‘2021-09-27’),
(‘104’, ‘9’, ‘2021-09-28’),
(‘104’, ‘7’, ‘2021-09-28’),
(‘104’, ‘8’, ‘2021-09-25’),
(‘104’, ‘3’, ‘2021-09-28’),
(‘104’, ‘11’, ‘2021-09-25’),
(‘104’, ‘6’, ‘2021-09-25’),
(‘104’, ‘12’, ‘2021-09-28’),
(‘105’, ‘8’, ‘2021-10-08’),
(‘105’, ‘9’, ‘2021-10-07’),
(‘105’, ‘7’, ‘2021-10-07’),
(‘105’, ‘11’, ‘2021-10-06’),
(‘105’, ‘5’, ‘2021-10-07’),
(‘105’, ‘4’, ‘2021-10-05’),
(‘105’, ‘10’, ‘2021-10-07’),
(‘106’, ‘12’, ‘2021-10-08’),
(‘106’, ‘1’, ‘2021-10-08’),
(‘106’, ‘4’, ‘2021-10-04’),
(‘106’, ‘5’, ‘2021-10-08’),
(‘106’, ‘2’, ‘2021-10-04’),
(‘106’, ‘6’, ‘2021-10-04’),
(‘106’, ‘7’, ‘2021-10-08’),
(‘107’, ‘5’, ‘2021-09-29’),
(‘107’, ‘3’, ‘2021-09-28’),
(‘107’, ‘10’, ‘2021-09-27’),
(‘108’, ‘9’, ‘2021-10-08’),
(‘108’, ‘3’, ‘2021-10-10’),
(‘108’, ‘8’, ‘2021-10-10’),
(‘108’, ‘10’, ‘2021-10-07’),
(‘108’, ‘11’, ‘2021-10-07’),
(‘109’, ‘2’, ‘2021-09-27’),
(‘109’, ‘4’, ‘2021-09-29’),
(‘109’, ‘5’, ‘2021-09-29’),
(‘109’, ‘9’, ‘2021-09-30’),
(‘109’, ‘8’, ‘2021-09-26’),
(‘1010’, ‘2’, ‘2021-09-29’),
(‘1010’, ‘9’, ‘2021-09-29’),
(‘1010’, ‘1’, ‘2021-10-01’);

2. 练习题

2.1 查询累积销量排名第二的商品

2.1.1 题目需求

查询订单明细表(order_detail)中销量(下单件数)排名第二的商品id,如果不存在返回null,如果存在多个排名第二的商品则需要全部返回。期望结果如下:
sku_id
2
2.1.2 代码实现
hive>
select sku_id
from (
select sku_id
from (
select sku_id,
order_num,
dense_rank() over (order by order_num desc) rk
from (
select sku_id,
sum(sku_num) order_num
from order_detail
group by sku_id
) t1
) t2
where rk = 2
) t3
right join --为保证,没有第二名的情况下,返回null
(
select 1
) t4
on 1 = 1;

2.2 查询至少连续三天下单的用户

2.2.1 题目需求

查询订单信息表(order_info)中最少连续3天下单的用户id,期望结果如下:
user_id
101

2.2.2 代码实现

hive>
select distinct user_id
from (
select user_id
from (
select user_id
, create_date
, date_sub(create_date, row_number() over (partition by user_id order by create_date)) flag
from (
select user_id
, create_date
from order_info
group by user_id, create_date
) t1 – 同一天可能多个用户下单,进行去重
) t2 – 判断一串日期是否连续:若连续,用这个日期减去它的排名,会得到一个相同的结果
group by user_id, flag
having count(flag) >= 3 – 连续下单大于等于三天
) t3;

2.3 查询各品类销售商品的种类数及销量最高的商品

2.3.1 题目需求

从订单明细表(order_detail)统计各品类销售出的商品种类数及累积销量最好的商品,期望结果如下:
category_id

(分类id) category_name

(分类名称) sku_id

(销量最好的商品id) name

(商品名称) order_num

(销量最好的商品销量) sku_cnt

(商品种类数量)
1 数码 2 手机壳 6044 4
2 厨卫 8 微波炉 253 4
3 户外 12 遮阳伞 20682 4

2.3.2 思路分析

(1)知识储备
 rank函数:对有序序列编号,当排序字段取值相同时编号相同,且下一条取值不同记录的编号不连续。如序列为:13,13,13,13,13,14,…对应的排序编号为1,1,1,1,1,6,…
 dense_rank函数:对有序序列编号,当排序字段相同时编号相同,且下一条记录的编号仍连续。如序列为:13,13,13,13,13,14,…对应的排序编号为1,1,1,1,1,2,…
 row_number函数:对有序序列编号,不考虑排序字段取值,每条记录的编号总比上一条增1,编号即行号。
(2)实现步骤
本题要对各品类下的商品销量做排名,只要获得各商品销量,就会演变为经典的分组TopN问题。至于需求中要求统计各品类下的商品销量,使用count函数即可解决。详细过程如下所示。
第一步:获取各商品销量(od子查询)。
商品销售记录存储在order_detail表中,查询该表,按照sku_id分组,对sku_num求和即可。
第二步:依据销量对各品类商品排名并统计商品种类(t1子查询)
① sku所属的品类信息category_id及商品名称name存储在sku_info表,品类名称category_name存储在category_info表。将od子查询通过sku_id列与sku_info表join连接,即可获取category_id,再通过category_id与category_info表join连接,即可获取category_name列。
② od子查询的所有sku_id一定都存在于sku_info表,以od子查询作为主表,left join等价于inner join,而sku_info表中不满足连接条件的数据即没有下单记录的商品,无须统计,因此使用内连接join即可。
③ 同理,sku所属的category_id一定在category_info中存在,category_info中不满足连接条件的数据为没有下单记录的品类,无须统计,因此使用内连接join即可。
④ 使用开窗函数,按照品类分区、商品销量倒序排列,为记录编号。此处要获取销量最高的商品,当多种商品销量均为该品类下最大时,这些记录都应保留,排名应相同,因此调用rank函数或dense_rank函数均可。
⑤ 使用开窗函数,按照品类分区,结合count(*)即可获得各品类下的商品数。
第三步:获取排名第一的商品
查询t1子查询,筛选排名为1的数据即可。
为便于分析,将od子查询与sku_info表连接的中间结果命名为tmp1子查询。

为便于分析,将tmp1与category_info表连接的中间结果命名为tmp2子查询。

为rk和sku_cnt列的获取流程。

2.3.3 代码实现

hive>
select category_id,
category_name,
sku_id,
name,
order_num,
sku_cnt
from (
select od.sku_id,
sku.name,
sku.category_id,
cate.category_name,
order_num,
rank() over (partition by sku.category_id order by order_num desc) rk,
count(*) over (partition by sku.category_id) sku_cnt
from (
select sku_id,
sum(sku_num) order_num
from order_detail
group by sku_id
) od
join
sku_info sku
on od.sku_id = sku.sku_id
join
category_info cate
on sku.category_id = cate.category_id
) t1
where rk = 1;

2.4 查询用户的累计消费金额及VIP等级

2.4.1 题目需求

从订单信息表(order_info)中统计每个用户截止其每个下单日期的累积消费金额,以及每个用户在其每个下单日期的VIP等级。
用户vip等级根据累积消费金额计算,计算规则如下:
设累积消费总额为X,
若0=<X<10000,则vip等级为普通会员
若10000<=X<30000,则vip等级为青铜会员
若30000<=X<50000,则vip等级为白银会员
若50000<=X<80000,则vip为黄金会员
若80000<=X<100000,则vip等级为白金会员
若X>=100000,则vip等级为钻石会员
期望结果如下:
user_id

(用户id) create_date

(下单日期) sum_so_far
<decimal(16,2)>
(截至每个下单日期的累计下单金额) vip_level

(每个下单日期的VIP等级)
101 2021-09-27 29000.00 青铜会员
101 2021-09-28 99500.00 白金会员
101 2021-09-29 142800.00 钻石会员
101 2021-09-30 143660.00 钻石会员
102 2021-10-01 171680.00 钻石会员
102 2021-10-02 177850.00 钻石会员
103 2021-10-02 69980.00 黄金会员
103 2021-10-03 75890.00 黄金会员
104 2021-10-03 89880.00 白金会员
105 2021-10-04 120100.00 钻石会员
106 2021-10-04 9390.00 普通会员
106 2021-10-05 119150.00 钻石会员
107 2021-10-05 69850.00 黄金会员
107 2021-10-06 124150.00 钻石会员
108 2021-10-06 101070.00 钻石会员
108 2021-10-07 155770.00 钻石会员
109 2020-10-08 24020.00 青铜会员
109 2021-10-07 153500.00 钻石会员
1010 2020-10-08 51950.00 黄金会员

2.4.2 代码实现

hive>
select user_id,
create_date,
cast(sum_so_far as decimal(16,2)) sum_so_far,
case
when sum_so_far >= 100000 then ‘钻石会员’
when sum_so_far >= 80000 then ‘白金会员’
when sum_so_far >= 50000 then ‘黄金会员’
when sum_so_far >= 30000 then ‘白银会员’
when sum_so_far >= 10000 then ‘青铜会员’
when sum_so_far >= 0 then ‘普通会员’
end vip_level
from (
select user_id,
create_date,
sum(total_amount_per_day) over (partition by user_id order by create_date) sum_so_far
from (
select user_id,
create_date,
sum(total_amount) total_amount_per_day
from order_info
group by user_id, create_date
) t1
) t2;

2.5 查询首次下单后第二天连续下单的用户比率

2.5.1 题目需求

从订单信息表(order_info)中查询首次下单后第二天仍然下单的用户占所有下单用户的比例,结果保留一位小数,使用百分数显示,期望结果如下:
percentage
60.0%

2.5.2 思路分析

(1)知识储备
 concat(string|binary A, string|binary B…):将string或binary类型的参数按顺序拼接为字符串,参数数量不限。
 round(double a, int d):四舍五入,返回保留d位小数的近似值。要注意:四舍五入只考虑d+1小数位,如:round(1.449,1)返回1.4,round(1.49,1)返回1.5。
(2)实现步骤
分析题目要求,此处要求比率,首先需要明确分子和分母。分子为首日下单后第二天连续下单的用户数量,分母为所有下过单的用户数量。分别求解分子分母,问题迎刃而解。
第一步:获取每个用户的所有下单日期(t1子查询)。
此处统计的是用户下单日期相关的指标,应查询order_info表,该表粒度为一个用户的一次下单记录,而我们只需要每个用户的下单日期,所以按照user_id和下单日期create_date列分组聚合,获取分组字段即可。

第二步:按照下单日期升序排列,为数据编号(t2子查询)。
使用开窗函数,按照user_id列分区,按照create_date列升序排列,编号即可。t1子查询得到的user_id和create_date为分组字段,不可能存在重复值,所以三种编号函数的结果都是一样的。此处调用rank函数,编号字段记为rk。

第三步:获取用户的首次和第二次下单日期(t3子查询)。
首次和第二次下单日期对应的rk分别为1和2,只需要rk<=2的数据。查询子查询t2,筛选rk列小于等于2的所有数据,然后按照user_id分组,对create_date取最小值,为首日下单日期,记为buy_date_first,最大值为第二次下单日期,记为buy_date_second。要注意:如果用户只在某一天下过单,即只有rk=1的数据,此处的buy_date_first等于buy_date_second,与事实不符。这个问题不会影响结果的正确性,下文详解。

第四步:获取最终结果。
datediff(buy_date_second, buy_date_first)函数的返回值有三类:0、1和大于1。
① 取值为0:用户只在一天下过单。
② 取值为1:用户首次下单后,在次日也下过单。
③ 取值大于1:用户在首次下单后,次日未下单,但次日之后下过单。
不难看出,如果用户在首日下单后次日下过单,上述函数的返回值必然为1,据此即可筛选满足条件的用户。针对t3子查询,按照user_id分组,因为user_id不会重复,因此不需要去重,组合调用sum(if(datediff(buy_date_second, buy_date_first) = 1, 1, 0))即可获得分子,行数count(*)即下过单的用户数量,获得分母。二者作比,调用round函数和concat函数处理计算结果,得到保留一位小数的百分数,即为本题要求的最终结果。

2.5.3 代码实现

hive>
select concat(round(sum(if(datediff(buy_date_second, buy_date_first) = 1, 1, 0)) / count(_) _ 100, 1), ‘%’) percentage
from (
select user_id,
min(create_date) buy_date_first,
max(create_date) buy_date_second
from (
select user_id,
create_date,
rank() over (partition by user_id order by create_date) rk
from (
select user_id,
create_date
from order_info
group by user_id, create_date
) t1
) t2
where rk <= 2
group by user_id
) t3;

2.6 每个商品销售首年的年份、销售数量和销售金额

2.6.1 题目需求

从订单明细表(order_detail)统计每个商品销售首年的年份,销售数量和销售总额。
期望结果如下:
sku_id

(商品id) year

(销售首年年份) order_num

(首年销量) order_amount
<decimal(16,2)>
(首年销售金额)
1 2020 2 4000.00
2 2020 26 260.00
3 2020 1 5000.00
4 2021 53 318000.00
5 2021 242 121000.00
6 2020 6 12000.00
7 2020 35 3500.00
8 2020 59 35400.00
9 2021 194 194000.00
10 2020 94 9400.00
11 2020 95 4750.00
12 2020 83 1660.00

2.6.2 思路分析

第一步:获取各商品销售首年年份。
① 要知道各商品销售首年的年份,有很多方式,这里介绍三种,如下。
a. 调用year函数,获取下单日期的年份,然后按sku_id分组,取年份最小值。
b. 按照sku_id分组取下单日期的最小值,然后调用year函数获取年份。
c. 使用开窗函数,按照sku_id分区,结合其它函数获取首年年份,下文详解。
如果只是获取下单首年的年份,三种方式均可。但是此处还要获取首年的销售数量和销售总额,前两种方式聚合之后每个sku_id只对应一条数据,丢失了金额和下单数量信息。读者可能会考虑使用sum(if())在获取下单首年年份时获取金额和数量,但仔细想想,if函数如何筛选首年记录?原表中并没有列可以直接让我们获取下单首年的年份,无法筛选这部分数据,这种思路无法实现。综上,方式a和b不可用,选择方式c。
第二步:筛选各商品的首年下单记录。
考虑下一个问题,如何筛选首年下单记录?这里提供两种思路,如下。
a. 使用开窗函数,按照sku_id分区,结合min函数和year函数获取首年年份,基于该字段即可获取首年下单记录。
b. 使用开窗函数,按照sku_id分区,按照下单年份升序排列,调用rank函数或dense_rank函数编号。不难发现,首年的下单记录,编号一定为1,根据这一规律筛选即可。
此处选择方式b。那么,首先要根据下单年份编号,对应t1子查询。

第三步:获取最终结果。
按照sku_id和下单年份分组聚合计算即可,不再赘述。

2.6.3 代码实现

hive>
select sku_id,
year(create_date) year,
cast(sum(sku_num) as bigint) order_num,
cast(sum(price*sku_num) as decimal(16,2)) order_amount
from (
select order_id,
sku_id,
price,
sku_num,
create_date,
rank() over (partition by sku_id order by year(create_date)) rk
from order_detail
) t1
where rk = 1
group by sku_id,year(create_date);

2.7 统计每个商品的销量最高的日期

2.7.1 题目需求

从订单明细表(order_detail)中统计出每种商品销售件数最多的日期及当日销量,如果有同一商品多日销量并列的情况,取其中的最小日期。期望结果如下:
sku_id
(商品id) create_date
(销量最高的日期) sum_num
(销量)
1 2021-09-30 9
2 2021-10-02 5800
3 2021-10-05 9
4 2021-10-07 10
5 2021-10-03 47
6 2021-10-03 8
7 2021-10-05 58
8 2020-10-08 59
9 2021-10-01 45
10 2020-10-08 94
11 2020-10-08 95
12 2021-10-03 20400

2.7.2 代码实现

hive>
select sku_id,
create_date,
sum_num
from (
select sku_id,
create_date,
sum_num,
row_number() over (partition by sku_id order by sum_num desc,create_date asc) rn
from (
select sku_id,
create_date,
sum(sku_num) sum_num
from order_detail
group by sku_id, create_date
) t1
) t2
where rn = 1;

2.8 查询销售件数高于品类平均数的商品

2.8.1 题目需求

从订单明细表(order_detail)中查询累积销售件数高于其所属品类平均数的商品,期望结果如下:
sku_id name sum_num cate_avg_num
2 手机壳 6044 1546
5 破壁机 242 194
7 热水壶 252 194
8 微波炉 253 194
12 遮阳伞 20682 5373

2.8.2 代码实现

hive>
select
t2.sku_id,
t2.name,
t2.sum_num,
t2.cate_avg_sum
from
(select
t1.sku_id,
t1.category_id,
t1.sum_num,
t1.name,
cast(avg(t1.sum_num)over(partition by t1.category_id)as bigint) cate_avg_sum
from
(
select
od.sku_id,
si.category_id,
si.name,
sum(od.sku_num) sum_num
from
order_detail od
join
sku_info si
on
od.sku_id=si.sku_id
group by
od.sku_id,si.category_id,si.name
)t1)t2
where
t2.sum_num>t2.cate_avg_sum;

2.9 查询指定日期的全部商品价格

2.9.1 题目需求

查询所有商品(sku_info表)截至到2021年10月01号的最新商品价格(需要结合价格修改表进行分析)
sku_id
(商品id) price<decimal(16,2)>
(商品价格)
1 2000.00
2 10.00
3 5000.00
4 6000.00
5 500.00
6 2000.00
7 100.00
8 600.00
9 1000.00
10 90.00
11 66.00
12 20.00

2.9.2 代码实现

hive>
select t3.sku_id,
cast(nvl(new_price, cast(t3.price as decimal(16,2))) as decimal(16,2)) price
from sku_info t3
left join
(
select sku_id,
new_price
from (
select sku_id,
new_price,
change_date,
row_number() over (partition by sku_id order by change_date desc) rn
from sku_price_modify_detail
where change_date <= ‘2021-10-01’
) t1
where rn = 1
) t2
on t3.sku_id = t2.sku_id;

2.10 即时订单比例

2.10.1 题目需求

订单配送中,如果期望配送日期和下单日期相同,称为即时订单,如果期望配送日期和下单日期不同,称为计划订单。
请从配送信息表(delivery_info)中求出每个用户的首单(用户的第一个订单)中即时订单的比例,保留两位小数,以小数形式显示。期望结果如下:
percentage<decimal(16,2)>
0.50

2.10.2 代码实现

hive>
select
cast(sum(if(order_date=custom_date,1,0))/count(*) as decimal(16,2)) percentage
from
(
select
delivery_id,
user_id,
order_date,
custom_date,
row_number() over (partition by user_id order by order_date) rn
from delivery_info
)t1
where rn=1;

2.11 查询所有用户的连续登录两天及以上的日期区间

2.11.1 题目需求

从登录明细表(user_login_detail)中查询出,所有用户的连续登录两天及以上的日期区间,以登录时间(login_ts)为准。期望结果如下:
user_id
(用户id) start_date
(开始日期) end_date
(结束日期)
101 2021-09-27 2021-09-30
102 2021-10-01 2021-10-02
106 2021-10-04 2021-10-05
107 2021-10-05 2021-10-06

2.11.2 思路分析

本题是经典的连续问题,解决这类问题的关键在于寻找连续数据的规律。分析题目要求,如果能将粒度处理为一个用户一天的登录记录,然后进行开窗,按照user_id列分区,登录日期login_ts列升序排列。通过date_sub函数,用登录日期减去排名,差值记为flag。只有用户连续登录时,数据的flag相同,因此只要相同的flag个数大于等于2就说明用户连续登录了两天及以上,实现步骤如下。
第一步:获取登录日期(t1子查询)。
user_login_detail表的粒度为一个用户的一次登录操作,此处只需要对每个用户每天保留一条记录即可,按照user_id和登录日期分组,直接获取分组字段。

第二步:按照日期排名(t2子查询)
使用开窗函数,按照user_id分区,login_date升序排列,同一用户的login_date不会重复,因此三种编号函数都可以使用,此处选用row_number函数。

第三步:获取最终结果。
调用date_sub函数,用login_date减去rn,得到flag。按照user_id和flag分组聚合,使用having子句筛选count(*)大于等于2的数据即可获得每个用户的连续登录记录,各个分组内login_date最小值为连续登录区间下限,最大值为区间上限。

2.11.3 代码实现

hive>
select user_id,
min(login_date) start_date,
max(login_date) end_date
from (
select user_id,
login_date,
date_sub(login_date, rn) flag
from (
select user_id,
login_date,
row_number() over (partition by user_id order by login_date) rn
from (
select user_id,
date_format(login_ts, ‘yyyy-MM-dd’) login_date
from user_login_detail
group by user_id, date_format(login_ts, ‘yyyy-MM-dd’)
) t1
) t2
) t3
group by user_id, flag
having count(*) >= 2;

2.12 查询出每个用户的最近三笔订单

2.12.1 题目需求

从订单信息表(order_info)中查询出每个用户的最近三笔订单,期望结果如下:
user_id
order_id
create_date

101 2 2021-09-28
101 3 2021-09-29
101 4 2021-09-30
102 5 2021-10-01
102 6 2021-10-01
102 8 2021-10-02
103 9 2021-10-02
103 10 2021-10-02
103 12 2021-10-03
104 13 2021-10-03
104 14 2021-10-03
104 15 2021-10-03
105 17 2021-10-04
105 18 2021-10-04
105 19 2021-10-04
106 22 2021-10-05
106 23 2021-10-05
106 24 2021-10-05
107 25 2021-10-05
107 27 2021-10-06
107 28 2021-10-06
108 29 2021-10-06
108 31 2021-10-07
108 32 2021-10-07
109 33 2021-10-07
109 35 2021-10-08
109 36 2021-10-08
1010 37 2021-10-08
1010 38 2021-10-08
1010 39 2020-10-08

2.12.2 代码实现

hive>
select user_id,
order_id,
create_date
from (
select user_id
, order_id
, create_date
, row_number() over (partition by user_id order by create_date desc) rk
from order_info
) t1
where rk <= 3;

2.13 查询每个用户登录日期的最大空档期

2.13.1 题目需求

从登录明细表(user_login_detail)中查询每个用户两个登录日期(以login_ts为准)之间的最大的空档期。统计最大空档期时,用户最后一次登录至今的空档也要考虑在内,假设今天为2021-10-10。期望结果如下:
user_id
(用户id) max_diff
(最大空档期)
101 10
102 9
103 10
104 9
105 6
106 5
107 10
108 4
109 10
1010 12

2.13.2 思路分析

(1)知识储备
lead(a[, lines, [default_value]):窗口函数,获取窗口内当前行之后lines行列a的值。
 a:列名称
 lines:目标行相对当前行的偏移量,可省略,默认为1,表示获取当前行后一行的数据。
 default_value:默认值,当目标行越界时取默认值,可省略,若此时越界返回null。
(2)实现步骤:
若想获取登录日期的最大空档期,只需要将相邻的两次登录日期相减即可。通过调用lag函数获取本次登录的前一个登录日期或调用lead函数获取本次登录的后一个登录日期均可达到要求。但本题要求统计最后一次登录至当前的天数,这就需要用当前日期减去最后一次登录日期,如果用lag函数,最后一次登录只能取到倒数第二个登录日期,不能计算出最后一次登录至今未登录的时间空档。而用lead函数则可以,我们可以通过指定lead函数的默认值将末次登录的下一日期指定为当日,即可达到目的,所以选用lead函数。
第一步:获取各用户每天的登录记录(t1子查询)。
用户登录数据存储在user_login_detail表,该表粒度为一个用户的一次登录记录,本题的统计指标都是基于登录日期计算的,原表粒度过细。我们需要将粒度转化为一个用户一天的登录记录。按照user_id和登录日期分组,取分组字段即可。

第二步:获取各用户的下一登录日期(t2子查询)
使用开窗函数,按照user_id分区、login_date排序,调用lead函数获取当前行后一行的登录日期,作为下一登录日期next_login_date,并将数据越界时的默认值定为当日。

第三步:调用datediff函数,通过next_login_date和login_date列计算天数差,作为空档期diff列,查询结果作为t3子查询。
第四步:计算最大空档期(最终结果)。
按照user_id分组,取天数差diff的最大值即为用户的最大空档期,即最终结果。

2.13.3 代码实现

hive>
select
user_id,
max(diff) max_diff
from
(
select
user_id,
datediff(next_login_date,login_date) diff
from
(
select
user_id,
login_date,
lead(login_date,1,‘2021-10-10’) over(partition by user_id order by login_date) next_login_date
from
(
select
user_id,
date_format(login_ts,‘yyyy-MM-dd’) login_date
from user_login_detail
group by user_id,date_format(login_ts,‘yyyy-MM-dd’)
)t1
)t2
)t3
group by user_id;

2.14 查询相同时刻多地登陆的用户

2.14.1 题目需求

从登录明细表(user_login_detail)中查询在相同时刻,多地登陆(ip_address不同)的用户,期望结果如下:
user_id
(用户id)
101
102
104
107

2.14.2 思路分析

登录明细表记录了用户每次登录的登入时间(login_ts)和登出时间(logout_ts),据此可以获得用户每次登录的时间区间,只要任意两个区间有交集就说明该用户在相同时间多地登录。对于同一用户的登录记录,按照login_ts升序排列,如果任一条数据的login_ts小于它之前所有记录中登出时间logout_ts的最大值,说明登录区间发生了交叉,据此可以筛选出目标数据。如图10-19所示,将五个区间分别绘制在了时间轴上,展示了登录区间可能出现的情况。[in1,out1]为登录区间1,[in2,out2]为登录区间2,以此类推。对五个区间分别分析如下。
 登录区间1之前没有登录记录。
 登录区间2之前的登录区间为[in1,out1],因为in2小于out1,说明登录区间发生了交叉。
 登录区间3之前的登录区间为[in1,out1]、[in2,out2],登出时间最大值为out1,而in3大于out1,说明与之前的登录区间未发生交叉。
 登录区间4之前的登录区间为[in1,out1]、[in2,out2]、[in3,out3],登出时间最大值为out3,因为in4小于out3,说明与之前的登录区间发生了交叉。
 登录区间5之前的登录区间为[in1,out1]、[in2,out2]、[in3,out3]、[in4,out4],登出时间最大值为out4,因为in5大于out4,说明与之前的登录区间未发生交叉。
经过上述分析,可以得出登录区间2和登录区间4是与其他登录区间发生交叉的交叉。

第一步:获取历史登录登出时间的最大值(t1子查询)。
使用开窗函数,按照user_id分区、login_ts升序排列,将窗口范围划分为第一行至当前行前一行,此时窗口内包含了当前行之前的所有历史登录记录,然后取logout_ts的最大值即可。

第二步:筛选目标数据(最终结果)。
对max_logout使用if进行判断,若max_logout为null或者小于本次登录时间,说明时间区间没有交叉,记为1,否则即为0。筛选所有标记为0的数据,记为目标数据,如图10-21所示。一名用户可能有多次同一时间多地登录行为,所以需要去重。

2.14.3 代码实现

hive>
select
distinct t2.user_id
from
(
select
t1.user_id,
if(t1.max_logout is null ,2,if(t1.max_logout<t1.login_ts,1,0)) flag
from
(
select
user_id,
login_ts,
logout_ts,
max(logout_ts)over(partition by user_id order by login_ts rows between unbounded preceding and 1 preceding) max_logout
from
user_login_detail
)t1
)t2
where
t2.flag=0;

2.15 销售额完成任务指标的商品

2.15.1 题目需求

商家要求每个商品每个月需要售卖出一定的销售总额
假设1号商品销售总额大于21000,2号商品销售总额大于10000,其余商品没有要求
请写出SQL从订单详情表中(order_detail)查询连续两个月销售总额大于等于任务总额的商品
结果如下:
sku_id
(商品id)
1

2.15.2 思路分析

(1)知识储备
add_months(string start_date, int num_months):返回start_date之后num_months个月的日期。
(2)实现步骤
分析题意,首先要获取每个商品每个月的销售额,然后筛选完成销售任务的数据。在此基础上, sku_id相同且月份连续的即所求数据。
第一步:获取1号和2号商品每个月的销售额,并筛选满足条件的销售记录(t1子查询)
① 筛选sku_id为1或2的商品,截取create_date的yyyy-MM部分,记为ymd,作为月份的唯一标识。要注意,此处不可以调用month()函数获取月份,因为该函数返回的是日期的月份部分,不同年份的相同月份取值相同,无法区分。
② 按照ymd和sku_id分组聚合,统计每个sku_id每月的销售额,如图10-23所示。通过having子句筛选完成销售任务的数据。

第二步:筛选连续记录(最终结果)。
使用开窗函数,按照sku_id分区、ymd升序排列,为数据编号。如果是同一sku_id的连续销售记录,ymd减去编号的结果(记为rymd)相同。利用这一规律可以筛选连续记录。有三点需要考虑,如下。
① t1中获取的日期是yyyy-MM格式,而add_months函数接收的第一个参数为yyyy-MM-dd格式,因此需要拼接日期部分,此处拼接“-01”即可。
② rymd相同的数据个数大于等于2,则对应记录是连续销售达标记录。
③ 同一商品可能有多次连续销售达标记录,要去重。

2.15.3 代码实现及步骤

hive>
– 求出1号商品 和 2号商品 每个月的购买总额 并过滤掉没有满足指标的商品
select
sku_id,
concat(substring(create_date,0,7),‘-01’) ymd,
sum(price*sku_num) sku_sum
from
order_detail
where
sku_id=1 or sku_id=2
group by
sku_id,substring(create_date,0,7)
having
(sku_id=1 and sku_sum>=21000) or (sku_id=2 and sku_sum>=10000)

– 判断是否为连续两个月
select
distinct t3.sku_id
from
(
select
t2.sku_id,
count()over(partition by t2.sku_id,t2.rymd) cn
from
(
select
t1.sku_id,
add_months(t1.ymd,-row_number()over(partition by t1.sku_id order by t1.ymd)) rymd
from
(
select
sku_id,
concat(substring(create_date,0,7),‘-01’) ymd,
sum(price
sku_num) sku_sum
from
order_detail
where
sku_id=1 or sku_id=2
group by
sku_id,substring(create_date,0,7)
having
(sku_id=1 and sku_sum>=21000) or (sku_id=2 and sku_sum>=10000)
)t1
)t2
)t3
where
t3.cn>=2

2.16 各品类销量前三的所有商品

2.16.1 题目需求

从订单详情表中(order_detail)和商品(sku_info)中查询各个品类销售数量前三的商品。如果该品类小于三个商品,则输出所有的商品。
结果如下:
sku_id
(商品id) category_id
(品类id)
2 1
4 1
1 1
8 2
7 2
5 2
12 3
11 3
10 3
2.16.2 代码实现
hive>
select
t2.sku_id,
t2.category_id
from
(
select
t1.sku_id,
si.category_id,
rank()over(partition by category_id order by t1.sku_sum desc) rk
from
(
select
sku_id,
sum(sku_num) sku_sum
from
order_detail
group by
sku_id
)t1
join
sku_info si
on
t1.sku_id=si.sku_id
)t2
where
t2.rk<=3;

2.17 各品类中商品价格的中位数

2.17.1 题目需求

从商品信息表(sku_info)中,统计每给分类中商品价格的中位数,如果某分类中商品个数为偶数,则输出中间两个价格的平均值,如果是奇数,则输出中间价格即可。
结果如下:
category_id
(品类id) medprice<decimal(16,2)>
(中位数)
1 3500.00
2 550.00
3 75.00

2.17.2 思路分析

要获取商品价格的中位数,首先要将商品信息表按照商品的价格升序或降序排名,然后根据商品种类数,确定中位数的排名,取出对应数据即可。
第一步:获取各品类下商品的价格排名及商品种类数(t1子查询)。
① 使用开窗函数,按照品类category_id分区,利用count函数统计行数即可获得商品种类数cn。
② 使用开窗函数,按照品类category_id分区、按照价格price升序排列,结合row_number函数为商品排名,记为rk。

第二步:计算商品价格的中位数。
判断各品类下商品数量的奇偶性,如果是奇数,rk=(cn+1)/2的记录price列的值是商品价格中位数;如果是偶数,rk=cn/2和rk=cn/2+1的记录price字段的均值是商品价格的中位数。可以发现,无论商品数量是奇数还是偶数,只要调用avg(price)函数即可求得中位数。

注意:
编号只能用row_number函数,不可以使用dense_rank或rank。三者的区别在于商品价格相同时的编号规则。考虑如下情形。
排名在中位数之前的商品价格存在重复,如果用rank()和dense_rank()排名都会导致中位数计算错误。假设商品价格序列为:21.00,22.00,22.00,22.00,23.00,应取排名3的商品价格作为中位数,如果调用dense_rank函数则排名分别为1,2,2,2,3,计算得中位数为23.00,显然与事实不符,中位数应为22.00;调用rank函数则排名分别为1,2,2,2,5,如果按照rk=3过滤,则取不到数据,中位数为null,显然也是不对的。而row_number函数排名不存在这样的问题。

2.17.3 代码实现

hive>
select category_id,
cast(avg(price) as decimal(16, 2)) medprice
from (select category_id,
price,
row_number() over (partition by category_id order by price) rk,
count(*) over (partition by category_id) cn
from sku_info) t1
where (cn % 2 = 0 and (rk = cn / 2 or rk = cn / 2 + 1))
or (cn % 2 = 1 and rk = (cn + 1) / 2)
group by category_id;

2.18 找出销售额连续3天超过100的商品

2.18.1 题目需求

从订单详情表(order_detail)中找出销售额连续3天超过100的商品
结果如下:
sku_id
(商品id)
1
10
11
12
2
3
4
5
6
7
8
9

2.18.2 代码实现

hive>
– 每个商品每天的销售总额
select
sku_id,
create_date,
sum(price*sku_num) sku_sum
from
order_detail
group by
sku_id,create_date
having
sku_sum>=100

– 判断连续三天以上
select
distinct t3.sku_id
from
(
select
t2.sku_id,
count()over(partition by t2.sku_id,t2.date_drk) cdrk
from
(
select
t1.sku_id,
t1.create_date,
date_sub(t1.create_date,rank()over(partition by t1.sku_id order by t1.create_date)) date_drk
from
(
select
sku_id,
create_date,
sum(price
sku_num) sku_sum
from
order_detail
group by
sku_id,create_date
having
sku_sum>=100
)t1
)t2
)t3
where
t3.cdrk>=3

2.19 求出商品连续售卖的时间区间

2.19.1 题目需求

从订单详情表(order_detail)中,求出商品连续售卖的时间区间
结果如下(截取部分):
sku_id
(商品id) start_date
(起始时间) end_date
(结束时间)
1 2020-10-8 2020-10-8
1 2021-9-27 2021-9-27
1 2021-9-30 2021-10-1
1 2021-10-3 2021-10-7
10 2020-10-8 2020-10-8
10 2021-10-2 2021-10-3
10 2021-10-5 2021-10-7
11 2020-10-8 2020-10-8
11 2021-10-2 2021-10-7
12 2020-10-8 2020-10-8
12 2021-9-30 2021-9-30
12 2021-10-2 2021-10-6
2 2020-10-8 2020-10-8
2 2021-10-1 2021-10-2
2 2021-10-4 2021-10-7
3 2020-10-8 2020-10-8
3 2021-9-27 2021-9-27
3 2021-10-1 2021-10-1
3 2021-10-3 2021-10-7
4 2021-9-28 2021-9-28
4 2021-10-1 2021-10-7
5 2021-9-28 2021-9-28
5 2021-10-2 2021-10-7
6 2020-10-8 2020-10-8
6 2021-10-1 2021-10-7
7 2020-10-8 2020-10-8
7 2021-9-29 2021-9-29
7 2021-10-1 2021-10-1
7 2021-10-3 2021-10-6
8 2020-10-8 2020-10-8
8 2021-9-29 2021-9-29
8 2021-10-1 2021-10-2
8 2021-10-4 2021-10-7
9 2021-9-29 2021-9-29
9 2021-10-1 2021-10-1
9 2021-10-4 2021-10-7

2.19.2 思路分析

本题为连续问题,上文中已经多次提及此类问题的解决思路,此处不再赘述。
第一步:t1子查询。
order_detail表中的一行数据为一个sku_id的一次下单操作,同一sku_id同一天可能有多条记录,需要去重。按照sku_id和create_date分组,取分组字段即可。

第二步:t2子查询。
使用开窗函数,按照sku_id分区、create_date升序排列,调用row_number函数排名。从t1获取的下单日期不会重复,因此三种排名函数结果相同,均可使用。

第三步:最终结果的获取。
调用date_add或date_sub函数,用create_date减去排名rk,记为drk。sku_id和drk相同的即连续下单记录。按照sku_id和drk分组,取create_date的最小值和最大值即可以获得连续售卖区间的下界和上界。

2.19.3 代码实现

hive>
select sku_id,
min(create_date) start_date,
max(create_date) end_date
from (select sku_id,
create_date,
row_number() over (partition by sku_id order by create_date) rk
from (select sku_id,
create_date
from order_detail
group by sku_id,
create_date) t1) t2
group by sku_id,
date_add(create_date, -rk);

2.20 查看每件商品的售价涨幅情况

2.20.1 题目需求

从商品价格变更明细表(sku_price_modify_detail),得到最近一次价格的涨幅情况,并按照涨幅升序排序。
结果如下:
sku_id
(商品id) price_change<decimal(16,2)>
(涨幅)
8 -200.00
9 -100.00
2 -70.00
11 -16.00
12 -15.00
3 1.00
5 10.00
10 10.00
7 12.00
6 12.00
1 100.00
4 400.00

2.20.2 代码实现

hive>
– 拿到最新的涨幅的情况
select
sku_id,
new_price,
lead(new_price,1,null)over(partition by sku_id order by change_date desc) last_price,
row_number() over (partition by sku_id order by change_date desc) rk
from
sku_price_modify_detail;

– 过滤 rk=1
select
t1.sku_id,
t1.new_price,
t1.last_price
from
(
select
sku_id,
new_price,
lead(new_price,1,null)over(partition by sku_id order by change_date desc) last_price,
row_number() over (partition by sku_id order by change_date desc) rk
from
sku_price_modify_detail
)t1
where
t1.rk=1;

– 用商品表进行外连接
select
si.sku_id,
if(t2.sku_id is null ,0,if(t2.last_price is null,t2.new_price-si.price,t2.new_price-t2.last_price)) price_change
from
sku_info si
left join
(
select
t1.sku_id,
t1.new_price,
t1.last_price
from
(
select
sku_id,
new_price,
lead(new_price,1,null)over(partition by sku_id order by change_date desc) last_price,
row_number() over (partition by sku_id order by change_date desc) rk
from
sku_price_modify_detail
)t1
where
t1.rk=1
)t2
on
si.sku_id=t2.sku_id
order by
price_change asc;

2.21 销售订单首购和末购分析

2.21.1 题目需求

通过商品信息表(sku_info)订单信息表(order_info)订单明细表(order_detail)分析如果有一个用户成功下单两个及两个以上的购买成功的手机订单(购买商品为xiaomi 10,apple 12,xiaomi 13)那么输出这个用户的id及第一次成功购买手机的日期和最后一次成功购买手机的日期,以及购买手机成功的次数。
结果如下:
user_id
(用户id) first_date
(首次时间) last_date
(末次时间) cn
(购买次数)
101 2021-09-27 2021-09-28 3
1010 2021-10-08 2021-10-08 2
102 2021-10-01 2021-10-01 3
103 2021-09-30 2021-10-02 2
104 2021-10-03 2021-10-03 3
105 2021-10-04 2021-10-04 2
106 2021-10-04 2021-10-05 3
107 2021-10-05 2021-10-05 3
108 2021-10-06 2021-10-06 3
109 2021-10-07 2021-10-07 3

2.21.2 代码实现

hive>
select
*
from
(
select
distinct
oi.user_id,
first_value(oi.create_date)over(partition by oi.user_id order by oi.create_date rows between unbounded preceding and unbounded following) first_order,
last_value(oi.create_date)over(partition by oi.user_id order by oi.create_date rows between unbounded preceding and unbounded following) last_order,
count(*)over(partition by oi.user_id order by oi.create_date rows between unbounded preceding and unbounded following) cn
from
order_info oi
join
order_detail od
on
oi.order_id=od.order_id
join
sku_info si
on
od.sku_id=si.sku_id
where
si.name in(‘xiaomi 10’,‘apple 12’,‘xiaomi 13’)
)t1
where
t1.cn>=2

2.22 统计活跃间隔对用户分级结果

2.22.1 题目需求

用户等级:
忠实用户:近7天活跃且非新用户
新晋用户:近7天新增
沉睡用户:近7天未活跃但是在7天前活跃
流失用户:近30天未活跃但是在30天前活跃
假设今天是数据中所有日期的最大值,从用户登录明细表中的用户登录时间给各用户分级,求出各等级用户的人数
结果如下:
level
(用户等级) cn
(用户数量)
忠实用户 6
新增用户 3
沉睡用户 1

2.22.2 思路分析

本题的关键在于划分用户等级,而用户等级的划分依赖于末次登录日期和注册日期。
第一步:计算用户注册日期及末次登录日期(t1子查询)。
查询user_login_detail表,按照user_id分组,调用min函数处理login_ts列,然后将其格式化为yyyy-MM-dd日期字符串,获得用户的注册日期register_date,同理,调用max函数获取用户的末次登录日期last_login_date。

第二步:计算当日日期(t2子查询)。
题目规定当日日期为所有登录日期的最大值,调用max函数和date_format函数求得cur_date。
第三步:划分用户等级(t3子查询)
将t1和t2连接在一起。t2实际上只有一条记录,使用笛卡尔积连接即可。
通过case when子句,根据注册时间register_date和末次登录日期last_login_date与当日的关系,按照题目要求为用户分级,字段名称记为degree。

第四步:统计各级用户人数(最终结果)。
按照degree分组,统计用户数即可。此处的user_id来源于t1子查询,而该字段是t1的分组字段,不可能重复,因此无须去重。

2.22.3 代码实现

hive>
select degree level,
count(user_id) cn
from (select user_id,
case
when register_date >= date_add(cur_date, -6) then ‘新晋用户’
when last_login_dt >= date_add(cur_date, -6) then ‘忠实用户’
when last_login_dt >= date_add(cur_date, -13) then ‘沉睡用户’
when last_login_dt < date_add(cur_date, -29) then ‘流失用户’
end degree
from (select user_id,
date_format(min(login_ts), ‘yyyy-MM-dd’) register_date,
date_format(max(login_ts), ‘yyyy-MM-dd’) last_login_dt
from user_login_detail
group by user_id) t1
join
(select date_format(max(login_ts), ‘yyyy-MM-dd’) cur_date
from user_login_detail) t2) t3
group by degree;

2.23 连续签到领金币数

2.23.1 题目需求

用户每天签到可以领1金币,并可以累计签到天数,连续签到的第3、7天分别可以额外领2和6金币。
每连续签到7天重新累积签到天数。
从用户登录明细表中求出每个用户金币总数,并按照金币总数倒序排序
结果如下:
user_id
(用户id) sum_coin_cn
(金币总数)
101 7
109 3
107 3
102 3
106 2
104 2
103 2
1010 2
108 1
105 1

2.23.2 代码实现

hive>
– 求连续并标志是连续的第几天
select
t1.user_id,
t1.login_date,
date_sub(t1.login_date,t1.rk) login_date_rk,
count(*)over(partition by t1.user_id, date_sub(t1.login_date,t1.rk) order by t1.login_date) counti_cn
from
(
select
user_id,
date_format(login_ts,‘yyyy-MM-dd’) login_date,
rank()over(partition by user_id order by date_format(login_ts,‘yyyy-MM-dd’)) rk
from
user_login_detail
group by
user_id,date_format(login_ts,‘yyyy-MM-dd’)
)t1

–求出金币数量,以及签到奖励的金币数量
select
t2.user_id,
max(t2.counti_cn)+sum(if(t2.counti_cn%3=0,2,0))+sum(if(t2.counti_cn%7=0,6,0)) coin_cn
from
(
select
t1.user_id,
t1.login_date,
date_sub(t1.login_date,t1.rk) login_date_rk,
count(*)over(partition by t1.user_id, date_sub(t1.login_date,t1.rk) order by t1.login_date) counti_cn
from
(
select
user_id,
date_format(login_ts,‘yyyy-MM-dd’) login_date,
rank()over(partition by user_id order by date_format(login_ts,‘yyyy-MM-dd’)) rk
from
user_login_detail
group by
user_id,date_format(login_ts,‘yyyy-MM-dd’)
)t1
)t2
group by
t2.user_id,t2.login_date_rk

– 求出每个用户的金币总数
select
t3.user_id,
sum(t3.coin_cn) sum_coin_cn
from
(
select
t2.user_id,
max(t2.counti_cn)+sum(if(t2.counti_cn%3=0,2,0))+sum(if(t2.counti_cn%7=0,6,0)) coin_cn
from
(
select
t1.user_id,
t1.login_date,
date_sub(t1.login_date,t1.rk) login_date_rk,
count(*)over(partition by t1.user_id, date_sub(t1.login_date,t1.rk) order by t1.login_date) counti_cn
from
(
select
user_id,
date_format(login_ts,‘yyyy-MM-dd’) login_date,
rank()over(partition by user_id order by date_format(login_ts,‘yyyy-MM-dd’)) rk
from
user_login_detail
group by
user_id,date_format(login_ts,‘yyyy-MM-dd’)
)t1
)t2
group by
t2.user_id,t2.login_date_rk
)t3
group by
t3.user_id
order by
sum_coin_cn desc

12.综合练习之高级SQL

第1题同时在线人数问题

1.1 题目需求

现有各直播间的用户访问记录表(live_events)如下,表中每行数据表达的信息为,一个用户何时进入了一个直播间,又在何时离开了该直播间。

| user_id
(用户id) | live_id
(直播间id) | in_datetime
(进入直播间的时间) | out_datetime
(离开直播间的时间) |
| — | — | — | — |
| 100 | 1 | 2021-12-1 19:30:00 | 2021-12-1 19:53:00 |
| 100 | 2 | 2021-12-1 21:01:00 | 2021-12-1 22:00:00 |
| 101 | 1 | 2021-12-1 19:05:00 | 2021-12-1 20:55:00 |

现要求统计各直播间最大同时在线人数,期望结果如下:

live_idmax_user_count
14
23
32

1.2 数据准备

1)建表语句
drop table if exists live_events;
create table if not exists live_events
(
user_id int comment ‘用户id’,
live_id int comment ‘直播id’,
in_datetime string comment ‘进入直播间时间’,
out_datetime string comment ‘离开直播间时间’
)
comment ‘直播间访问记录’;
2)数据装载
INSERT overwrite table live_events
VALUES (100, 1, ‘2021-12-01 19:00:00’, ‘2021-12-01 19:28:00’),
(100, 1, ‘2021-12-01 19:30:00’, ‘2021-12-01 19:53:00’),
(100, 2, ‘2021-12-01 21:01:00’, ‘2021-12-01 22:00:00’),
(101, 1, ‘2021-12-01 19:05:00’, ‘2021-12-01 20:55:00’),
(101, 2, ‘2021-12-01 21:05:00’, ‘2021-12-01 21:58:00’),
(102, 1, ‘2021-12-01 19:10:00’, ‘2021-12-01 19:25:00’),
(102, 2, ‘2021-12-01 19:55:00’, ‘2021-12-01 21:00:00’),
(102, 3, ‘2021-12-01 21:05:00’, ‘2021-12-01 22:05:00’),
(104, 1, ‘2021-12-01 19:00:00’, ‘2021-12-01 20:59:00’),
(104, 2, ‘2021-12-01 21:57:00’, ‘2021-12-01 22:56:00’),
(105, 2, ‘2021-12-01 19:10:00’, ‘2021-12-01 19:18:00’),
(106, 3, ‘2021-12-01 19:01:00’, ‘2021-12-01 21:10:00’);

1.3 思路分析

每有一名用户进入,直播间人数加1,每有一名用户离开,直播间人数减1。根据这一规律,我们可以将直播间的登入登出操作分离,用额外的字段记录人数变化,然后借助开窗函数按照时间升序排列,计算截止当前行(当前时间)的直播间人数,最后取最大值即可。
第一步:分离登入登出操作(t1子查询)
① 取每条数据的user_id,live_id,in_datetime字段,补充user_change字段,取值为1。
② 取每条数据的user_id,live_id,out_datetime字段,补充user_change字段,取值-1。
③ 将①和②的结果通过union联合在一起,结果作为子查询t1。同一用户不可能在同一时间多次登入或登出同一直播间,因而数据不会重复,union all和union结果相同。此处使用union all。
第二步:计算截至当前行的直播间人数(t2子查询)。
使用开窗函数,按照live_id分区、event_time升序排列。
调用sum(user_change)即可计算累计人数,获得的结果作为子查询t2。窗口范围为默认值unbound preceding至current row,正好符合题目需求。
第三步:针对子查询t2,按照live_id分区,取user_count的最大值即为各直播间最大同时在线人数。

1.4 代码实现

select
live_id,
max(user_count) max_user_count
from
(
select
user_id,
live_id,
sum(user_change) over(partition by live_id order by event_time) user_count
from
(
select user_id,
live_id,
in_datetime event_time,
1 user_change
from live_events
union all
select user_id,
live_id,
out_datetime,
-1
from live_events
)t1
)t2
group by live_id;

第2题会话划分问题

2.1 题目需求

现有页面浏览记录表(page_view_events)如下,表中有每个用户的每次页面访问记录。

user_idpage_idview_timestamp
100home1659950435
100good_search1659950446
100good_list1659950457
100home1659950541
100good_detail1659950552
100cart1659950563
101home1659950435
101good_search1659950446
101good_list1659950457
101home1659950541
101good_detail1659950552
101cart1659950563
102home1659950435
102good_search1659950446
102good_list1659950457
103home1659950541
103good_detail1659950552
103cart1659950563

规定若同一用户的相邻两次访问记录时间间隔小于60s,则认为两次浏览记录属于同一会话。现有如下需求,为属于同一会话的访问记录增加一个相同的会话id字段,期望结果如下:

user_idpage_idview_timestampsession_id
100home1659950435100-1
100good_search1659950446100-1
100good_list1659950457100-1
100home1659950541100-2
100good_detail1659950552100-2
100cart1659950563100-2
101home1659950435101-1
101good_search1659950446101-1
101good_list1659950457101-1
101home1659950541101-2
101good_detail1659950552101-2
101cart1659950563101-2
102home1659950435102-1
102good_search1659950446102-1
102good_list1659950457102-1
103home1659950541103-1
103good_detail1659950552103-1

2.2 数据准备

1)建表语句
drop table if exists page_view_events;
create table if not exists page_view_events
(
user_id int comment ‘用户id’,
page_id string comment ‘页面id’,
view_timestamp bigint comment ‘访问时间戳’
)
comment ‘页面访问记录’;
2)数据装载
insert overwrite table page_view_events
values (100, ‘home’, 1659950435),
(100, ‘good_search’, 1659950446),
(100, ‘good_list’, 1659950457),
(100, ‘home’, 1659950541),
(100, ‘good_detail’, 1659950552),
(100, ‘cart’, 1659950563),
(101, ‘home’, 1659950435),
(101, ‘good_search’, 1659950446),
(101, ‘good_list’, 1659950457),
(101, ‘home’, 1659950541),
(101, ‘good_detail’, 1659950552),
(101, ‘cart’, 1659950563),
(102, ‘home’, 1659950435),
(102, ‘good_search’, 1659950446),
(102, ‘good_list’, 1659950457),
(103, ‘home’, 1659950541),
(103, ‘good_detail’, 1659950552),
(103, ‘cart’, 1659950563);

2.3 思路分析

只要同一用户的两条页面浏览记录的时间差小于60秒就被归为一个会话。显然,首先要获得用户每次浏览和上次浏览的时间差,可以借助lag函数实现。
页面可以被分为两类:会话起始页和非起始页。前者与上次浏览的时间差大于等于60秒,后者与上次浏览的时间差小于60秒。根据这个特点可以将二者区分开来。
session_id由user_id和一个递增序号拼接而成,会话起始时间越大,序号越大,且同一会话所有页面的序号相同。如果将所有页面浏览记录按照访问时间升序排列,序号只有在遇到会话起始页时需要加一。由此,引出本题的解决思路:根据当前浏览记录和上次浏览记录的时间差的不同,区分会话起始页和非起始页。定义列session_start_point,会话起始页赋值为1,非起始页为0。
使用开窗函数,按照user_id分区、view_timestamp升序排列,统计截至当前行的session_start_point列的和,与user_id拼接在一起即可得到session_id。
第一步:获取上次页面浏览的访问时间(t1子查询)
开窗,按照user_id分区、view_timestamp升序排列,调用lag函数获取上一条数据view_timestamp的值。要注意,获取用户首次浏览的上次访问时间,返回值默认为null,为便于计算,此处赋默认值0,此时当前行减去上一行远大于60s,首次访问必然会被视为会话的起始页面,与事实相符。
第二步:计算session_start_point(t2子查询)。
第三步:拼接得到session_id(最终结果)。

2.4 代码实现

select user_id,
page_id,
view_timestamp,
concat(user_id, ‘-’, sum(session_start_point) over (partition by user_id order by view_timestamp)) session_id
from (
select user_id,
page_id,
view_timestamp,
if(view_timestamp - lagts >= 60, 1, 0) session_start_point
from (
select user_id,
page_id,
view_timestamp,
lag(view_timestamp, 1, 0) over (partition by user_id order by view_timestamp) lagts
from page_view_events
) t1
) t2;

第3题间断连续登录用户问题

3.1 题目需求

现有各用户的登录记录表(login_events)如下,表中每行数据表达的信息是一个用户何时登录了平台。

user_idlogin_datetime
1002021-12-01 19:00:00
1002021-12-01 19:30:00
1002021-12-02 21:01:00

现要求统计各用户最长的连续登录天数,间断一天也算作连续,例如:一个用户在1,3,5,6登录,则视为连续6天登录。期望结果如下:

user_idmax_day_count
1003
1016
1023
1043
1051

3.2 数据准备

1)建表语句
drop table if exists login_events;
create table if not exists login_events
(
user_id int comment ‘用户id’,
login_datetime string comment ‘登录时间’
)
comment ‘直播间访问记录’;
2)数据装载
INSERT overwrite table login_events
VALUES (100, ‘2021-12-01 19:00:00’),
(100, ‘2021-12-01 19:30:00’),
(100, ‘2021-12-02 21:01:00’),
(100, ‘2021-12-03 11:01:00’),
(101, ‘2021-12-01 19:05:00’),
(101, ‘2021-12-01 21:05:00’),
(101, ‘2021-12-03 21:05:00’),
(101, ‘2021-12-05 15:05:00’),
(101, ‘2021-12-06 19:05:00’),
(102, ‘2021-12-01 19:55:00’),
(102, ‘2021-12-01 21:05:00’),
(102, ‘2021-12-02 21:57:00’),
(102, ‘2021-12-03 19:10:00’),
(104, ‘2021-12-04 21:57:00’),
(104, ‘2021-12-02 22:57:00’),
(105, ‘2021-12-01 10:01:00’);

3.3 思路分析

(1)知识储备
last_value(a, true):获取当前窗口内最后一条数据a字段的值,true表示忽略null值。
(2)执行步骤
与上一题相同,本题也是会话划分问题。使用开窗函数获取上次登录日期,通过当前日期与上次登录日期的差值划分连续登录会话。然后按照会话分组,求得每个会话的首次登录日期和末次登录日期,即可获得持续天数,最后取所有会话持续天数的最大值即可。
第一步:将粒度聚合为user_id+login_dt(t1子查询)
按照user_id和格式化后的login_dt分组,取分区字段即可。
第二步:计算本次登录日期和上次登录日期的天数差diff列(t2子查询)
① 通过开窗函数lag获取同一用户每个登录日期的上次登录日期。
② 计算本次日期和上次日期的天数差。要注意,首次登录的上次登录日期为null,如果直接调用datediff求解会抛出异常,所以在使用lag函数时要赋默认值,赋默认值为“0000-00-00”,默认值的选择不能影响结果的正确性,下文详解。
第三步:划分会话。
上一题我们介绍了一种划分会话的方式,此处介绍第二种。
对diff列的值进行判断,根据判断结果为dt列赋值。如果diff大于2,则当天为会话的起始登录日期,将dt赋值为当日,否则为null。
针对上述查询结果执行开窗函数,按照user_id分区、login_dt升序排列,调用last_value函数取当前窗口范围内dt列的最后一条不为null的数据,这样一来处于相同会话的数据,返回值也会相同,记为group_dt。
显然,每个用户首次登录日期一定为登录会话的起始日期,只要保证它与上次日期的天数差大于2即可,因此上一步将lag函数的默认值定为“0000-00-00”是可行的。
第四步:最终结果。
按照user_id和group_dt分组,计算会话内最大登录日期和最小登录日期的天数差,再加1,即为该会话的持续天数,最后取同一用户所有会话持续天数的最大值即为最终结果。

3.4 代码实现

select
user_id,
max(recent_days) max_recent_days --求出每个用户最大的连续天数
from
(
select
user_id,
user_flag,
datediff(max(login_date),min(login_date)) + 1 recent_days --按照分组求每个用户每次连续的天数(记得加1)
from
(
select
user_id,
login_date,
lag1_date,
concat(user_id,‘_’,flag) user_flag --拼接用户和标签分组
from
(
select
user_id,
login_date,
lag1_date,
sum(if(datediff(login_date,lag1_date)>2,1,0)) over(partition by user_id order by login_date) flag --获取大于2的标签
from
(
select
user_id,
login_date,
lag(login_date,1,‘1970-01-01’) over(partition by user_id order by login_date) lag1_date --获取上一次登录日期
from
(
select
user_id,
date_format(login_datetime,‘yyyy-MM-dd’) login_date
from login_events
group by user_id,date_format(login_datetime,‘yyyy-MM-dd’) --按照用户和日期去重
)t1
)t2
)t3
)t4
group by user_id,user_flag
)t5
group by user_id;

第4题日期交叉问题

4.1 题目需求

现有各品牌优惠周期表(promotion_info)如下,其记录了每个品牌的每个优惠活动的周期,其中同一品牌的不同优惠活动的周期可能会有交叉。

promotion_idbrandstart_dateend_date
1oppo2021-06-052021-06-09
2oppo2021-06-112021-06-21
3vivo2021-06-052021-06-15

现要求统计每个品牌的优惠总天数,若某个品牌在同一天有多个优惠活动,则只按一天计算。期望结果如下:

brandpromotion_day_count
vivo17
oppo16
redmi22
huawei22

4.2 数据准备

1)建表语句
drop table if exists promotion_info;
create table promotion_info
(
promotion_id string comment ‘优惠活动id’,
brand string comment ‘优惠品牌’,
start_date string comment ‘优惠活动开始日期’,
end_date string comment ‘优惠活动结束日期’
) comment ‘各品牌活动周期表’;
2)数据装载
insert overwrite table promotion_info
values (1, ‘oppo’, ‘2021-06-05’, ‘2021-06-09’),
(2, ‘oppo’, ‘2021-06-11’, ‘2021-06-21’),
(3, ‘vivo’, ‘2021-06-05’, ‘2021-06-15’),
(4, ‘vivo’, ‘2021-06-09’, ‘2021-06-21’),
(5, ‘redmi’, ‘2021-06-05’, ‘2021-06-21’),
(6, ‘redmi’, ‘2021-06-09’, ‘2021-06-15’),
(7, ‘redmi’, ‘2021-06-17’, ‘2021-06-26’),
(8, ‘huawei’, ‘2021-06-05’, ‘2021-06-26’),
(9, ‘huawei’, ‘2021-06-09’, ‘2021-06-15’),
(10, ‘huawei’, ‘2021-06-17’, ‘2021-06-21’);

4.3 思路分析

分析题意,只要调整区间范围,使得区间不存在交叉,统计每个区间的持续天数,最后对天数求和即可。
第一步:获取截至当前的历史活动的最大结束日期(t1子查询)
要找到所有存在交叉的区间,只要判断每个区间和历史区间是否存在交叉即可。而某个区间和历史区间存在交叉,等价于该区间历史活动end_date的最大值大于等于当前活动的start_date。因此,首先需要获取截至当前的历史活动的end_date的最大值。
使用开窗函数,按照brand分区、start_date排序,限定窗口范围为第一行至当前行的前一行,调用max函数计算结束日期end_date的最大值即可。
第二步:处理交叉区间(t2子查询)。
对t1子查询的max_end_date列进行判断,max_end_date列存在以下三种情况。
①max_end_date为空,说明是该品牌的第一次促销活动,start_date不变。
②max_end_date小于start_date,说明本次活动区间与历史活动无交叉,start_date不变。
③max_end_date大于start_date,存在区间交叉,将start_date赋值为max_end_date加1。
经过上述处理,交叉区间的交集为空,并集连续,结果作为t2子查询。
第三步:最终结果
区间交叉有一种特殊情况,即某个区间被另外的区间包含,此时经过第二步的处理,该区间的start_date会大于end_date,这部分活动区间是可以舍弃的,因为该活动的所有日期都会被其它活动区间统计。因此,这里筛选start_date小于等于end_date的区间,使用datediff函数计算每个期间的持续天数,最后按照brand分组求和即可。

4.4 代码实现

select
brand,
sum(datediff(end_date,start_date)+1) promotion_day_count
from
(
select
brand,
max_end_date,
if(max_end_date is null or start_date>max_end_date,start_date,date_add(max_end_date,1)) start_date,
end_date
from
(
select
brand,
start_date,
end_date,
max(end_date) over(partition by brand order by start_date rows between unbounded preceding and 1 preceding) max_end_date
from promotion_info
)t1
)t2
where end_date>start_date
group by brand;

举报

相关推荐

0 条评论