数据表和需求
表名:student, 结构和具体内容如下:
name course score
zs Math 100
zs Engl 80
ls Math 90
ls Engl 70
需求如下:
1、获取每个学生最高分对应的学科和分数
(方式一:使用row_number()开窗函数;方式二:不用开窗函数,使用join方式)
扩展:返回每一门课程和对应的最高分的学生姓名(思路:将分组的学生替换为学科即可,此处不再赘述)
2、获取每门课程都高于平均分的人、课程和分数
数据准备
set hive.exec.mode.local.auto=true;
set hive.cli.print.header=true;
create table student stored as orc as
select 'zs' as name, 'Math' as course, 100 as score union all
select 'zs' as name, 'Engl' as course, 80 as score union all
select 'ls' as name, 'Math' as course, 90 as score union all
select 'ls' as name, 'Engl' as course, 70 as score;
需求1:获取每个人最高分对应的学科和分数
第一步:使用开窗函数,用row_number(), 按name分组,每个人的分数排序,
第二步:排好序后,取出每个人的最高分即可
----------------- 方式一 使用row_number() ---------------
select
name,
course,
score
from(
select
name,
course,
score,
row_number() over(partition by name order by score desc) row_num
from student
)t
where row_num = 1;
----------------- 查询结果 ---------------
name course score
ls Math 90
zs Math 100
扩展:返回每一门课程和对应的最高分的学生姓名(思路:用学科分组替换学生分组,不再赘述)
----------------- 方式二 使用join ---------------
--思路:先获取每个学生的最高分,去join原表即可拿到
select
s.name,
s.course,
s.score
from student s
join(
select
name,
max(score) as max_score
from student
group by name
)t
on s.name = t.name and s.score = t.max_score;
----------------- 查询结果 ---------------
s.name s.course s.score
ls Math 90
zs Math 100
需求2:获取每门课程都高于平均分的人、课程和分数
减法实现:所有课程高于平均分的人 = 所有人 - 至少一门低于平均分的人
第一步:先求每门课程的平均分,得到临时表 t1
第二步:找出有某门课程小于平均分的人,得到临时表 t2
第三步:原表left join t2,让 t2 中 name 为空,则反向获取到每门课程均高于平均分的人
注意点:有一个思维转换:不容易直接找出同时多门课程都高于平均分的人,
可以反向找出只要有一门课程小于平均分,那这个人一定不是所有课程都高于平均分的人
-------- 测试查询 满足条件的人和课程;但是多行,最终每个人需转为一行数据,见下一条sql -----
select
name,
course,
score
from abc
left join(
select
if(abc.score < t1.avg_score, abc.name, null) as name,
from abc
join (
select
cource
sum(score) / count(*) as avg_score
from abc
group by course
)t1
on abc.course = t1.course
)t2
on abc.name = t2.name
where t2.name is null;
----------------- 查询结果 ---------------
s.name s.course s.score
zs Math 100
zs Engl 80
另外注意点: if(s.score < t1.avg_score, s.name, rand()) as name
判断分数低于平均分时,如果非,则用rand()代替,这样避免后面join时出现,倾斜key
-------- 最终sql查询 满足条件的人和课程;但是多行,最终每个人转一行数据 -----
create table tmp stored as orc as
select
s.name,
collect_list(concat_ws(':', array(s.course, s.score))) as detail_array,
concat_ws(',',
collect_list( concat_ws(':', array(s.course, s.score) ) )
) as detail_string
from student s
left join(
select
if(s.score < t1.avg_score, s.name, rand()) as name
from student s
join (
select
course,
sum(score) / count(*) as avg_score
from student
group by course
)t1
on s.course = t1.course
)t2
on s.name = t2.name
where t2.name is null
group by s.name;
----------------- 查询结果 ---------------
s.name detail_array detail_string
zs ["Math:100","Engl:80"] Math:100,Engl:80
注意:字段类型根据最终需求看是否转 string,还是array
(1) string更容易和其他数据库间导入导出,如mysql没有array类型,可能会有一点麻烦
(2) array容易获取对应位置的数据
hive> desc tmp;
OK
col_name data_type comment
name string
detail_array array<string>
detail_string string
----------获取某一个位置的值
hive> select detail_array[1] as course_score from tmp;
course_score
Engl:80
hive> select detail_array[0] as course_score from tmp;
course_score
Math:100
3、获取每个学科都及格(>=60分)的学生
--方式一: 使用having方式,仅一个job(比较好方式) ----------
select
name
from student_copy
group by name
having min(score)> 60;
--结果如下: ------------
ls
zs
Time taken: 1.305 seconds, Fetched: 2 row(s)
-- 比较笨重的方式:会有4个job(不建议,只是对比两个) -------
select
distinct s.name
from student_copy s
left join (
select
name
from student_copy
where score < 60
group by name
)t1
on s.name=t1.name
where t1.name is null;
--结果如下
ls
zs
Time taken: 12.298 seconds, Fetched: 4 row(s)