hive sql面试题(源码级解读如何解决Spark-sql读取hive分区表执行效率低问题)

本文目录
- 源码级解读如何解决Spark-sql读取hive分区表执行效率低问题
- Hive中的全排序:order by,sort by, distribute by
- 数据分析课程笔记 - 20 - HIVE 核心技能之窗口函数
- hive not in 通过什么实现
源码级解读如何解决Spark-sql读取hive分区表执行效率低问题
问题描述
在开发过程中使用spark去读取hive分区表的过程中(或者使用hive on spark、nodepad开发工具),部分开发人员未注意添加分区属性过滤导致在执行过程中加载了全量数据,引起任务执行效率低、磁盘IO大量损耗等问题。
解决办法
1、自定义规则CheckPartitionTable类,实现Rule,通过以下方式创建SparkSession。
2、自定义规则CheckPartitionTable类,实现Rule,将规则类追加至Optimizer.batches: Seq中,如下。
规则内容实现
1、CheckPartitionTable规则执行类,需要通过引入sparkSession从而获取到引入conf;需要继承Rule;
2、通过splitPredicates方法,分离分区谓词,得到分区谓词表达式。在sql解析过程中将谓词解析为TreeNode,此处采用递归的方式获取分区谓词。
3、判断是否是分区表,且是否添加分区字段。
4、实现Rule的apply方法
大数据和云计算的关系
大数据JUC面试题
大数据之Kafka集群部署
大数据logstsh架构
大数据技术kafka的零拷贝
Hive中的全排序:order by,sort by, distribute by
写mapreduce程序时,如果reduce个数》1,想要实现全排序需要控制好map的输出,详见 Hadoop 简单实现全排序
现在学了hive,写sql大家都很熟悉,如果一个order by解决了全排序还用那么麻烦写mapreduce函数吗?
事实上,hive使用order by会默认设置reduce的个数=1,既然reducer的个数都是1了,结果自然全排序!
这也违背了充分利用分布式计算进行海量数据排序的初衷,效率低下。
那么hive又提供了一个可供选择的方式:sort by
它会保证每个reducer的输出文件是有序的(其实是废话,每个reducer的输出当然是有序的!),要想实现全排序,还得加一个order by的过程,就是对sort by的reduce输出结果再进行一次排序。
所以:
要想用hive实现全排序:
要么用order by,但这样默认了reducer个数为1,效率低下。
要么用sort by+order by,sort by过程可以设置reducer个数(n),order by过程用n个reducer的输出文件进行一次全排序,得到最终的结果。
(个人理解,如有错误请不吝赐教,感谢!)
注:
(1)对于order by,sort
by:
我们可以使用limit进行限制返回的行数,从而实现抓出数据的top N的情形。
(2)对于distribute by:
sort by为每个reducer产生一个排序文件。在有些情况下,你需要控制某个特定行应该到哪个reducer,通常是为了进行后续的聚集操作。hive的distribute by就派上用场了:
From table
select year, temperature
distribute by year
sort by year asc, temperature desc;
上面实现了局部排序,且规定了:根据年份和气温对气象数据进行排序,以确保所有具有相同年份的行最终都在一个reducer分区中(文件下),可以看出,distribute by经常与sort by一起使用。
需要注意的是,hive要求distribute by要写在sort by之前。
(3)对于cluster by:
简而言之:cluster by column=distribute by column+sort by column(注意,都是针对column列,且采用默认ASC)
即对于上面例子:
From table
select year, temperature
cluster by year;
就等于:
From table
select year, temperature
distribute by year
sort by year;
当然这失去了按照气温排序的要求。
数据分析课程笔记 - 20 - HIVE 核心技能之窗口函数
大家好呀,这节课我们学习 Hive 核心技能中最难的部分——窗口函数。窗口函数我们之前在学 MySQL 的时候有学过一些,但是只学了三个排序的窗口函数。这节课我们会学习更多的窗口函数,包括累计计算、分区排序、切片排序以及偏移分析。
在正式学习之前,我们需要先明确一下窗口函数和GROUP BY分组的区别。二者在功能上有相似之处,但是它们存在本质区别。
1. 分组会改变表的结构,而窗口函数不会改变表的结构。比如原表有10行数据,分成两组后只有两行,而窗口函数仍然返回十行数据。
2. 分组只能查询分组后的字段,包括分组字段(组名)和聚合函数字段。而窗口函数对查询字段没有限制,也就是可以查询原表的任意字段,再加上窗口函数新增的一列值。
好啦,现在让我们一起进入窗口函数的世界吧~
本节课主要内容:
1、累计计算窗口函数
(1)sum(…) over(……)
(2)avg(…) over(……)
(3)语法总结
2、分区排序窗口函数
(1)row_number()
(2)rank()
(3)dense_rank()
3、切片排序窗口函数
(1)ntile(n) over(……)
4、偏移分析窗口函数
5、重点练习
大家在做报表的时候,经常会遇到计算截止某月的累计数值,通常在EXCEL里可以通过函数来实现。
那么在HiveSQL里,该如何实现这种累计数值的计算呢?那就是利用窗口函数!
关于窗口函数的几点说明:
需求分析 :既然要进行按月累计,我们就先要把2018年的每笔交易时间转换成月并按月分组聚合计算,得出一个2018年每月支付金额总合表,再基于这张表用窗口函数进行累计计算。
2018年每月支付金额总和表:
再用窗口函数进行月度累计:
年度进行汇总。
这个需求比需求1多了一个需求,那就是年度汇总。那我们只需要在上个需求的子查询中加一个 year 字段即可。
说明:
1、over 中的 partition by 起到了窗口内将数据分组的作用。事实上,加上partition by之后,可以理解为分成了多个窗口,并在每个窗口内进行累加计算或者分区。
如果不加 partition by a.year 的话,运行结果就是这样单纯按月份进行分组的:
2、order by 按照什么顺序进行累加,升序ASC、降序DESC,默认是升序。
大家看股票的时候,经常会看到这种K线图,里面经常用到的就是7日、30日移动平均的趋势图,那如何使用窗口函数来计算移动平均值呢?
需求分析 :这个需求要求每个月近三个月的移动平均支付金额,这里我们要用到一个新知识点,在窗口函数 avg over 的 order by a.month 之后加一句 rows between 2 preceding and current row 来设定计算移动平均的范围,这个语句的含义就是包含本行及前两行。其他部分的写法跟前面的需求类似,先取出2018年每个月的支付金额总和,再用窗口函数求移动平均。
注意:
sum(…A…) over(partition by …B… order by …C… rows between …D1… and …D2…)
avg(…A…) over(partition by …B… order by …C… rows between…D1… and …D2…)
A:需要被加工的字段名称
B:分组的字段名称
C:排序的字段名称
D:计算的行数范围
rows between unbounded preceding and current row
——包括本行和之前所有的行
rows between current row and unbounded following
——包括本行和之后所有的行
rows between 3 preceding and current row
——包括本行以内和前三行
rows between 3 preceding and 1 following
——从前三行到下一行(5行)
max(……) over(partition by …… order by …… rows between ……and ……)
min(……) over(partition by …… order by …… rows between ……and ……)
row_number() 、rank()、dense_rank()
用法:这三个函数的作用都是返回相应规则的排序序号
row_number() over(partition by …A… order by …B… )
rank() over(partition by …A… order by …B… )
dense_rank() over(partition by …A… order by …B… )
A:分组的字段名称
B:排序的字段名称
注意: 这3个函数的括号内是不加任何字段名称的!
row_number :它会为查询出来的每一行记录生成一个序号,依次排序且不会重复。
rank&dense_rank :在各个分组内, rank() 是跳跃排序,有两个第一名时接下来就是第三名, dense_rank() 是连续排序,有两个第一名时仍然跟着第二名。
实例练习:
再眼熟一下 user_trade 的表结构:
需求分析 :先限定时间范围,然后根据 user_name 进行分组,接着选出 分组去重后的 user_name,并计算每个用户 goods_category 的数量(记得 distinct 去重),再然后就是用窗口函数对 goods_category 的数量进行排序,当然选择哪一种排序方法要看具体要求,这里我们可以三种方法都试一下看看结果:
注意 :窗口函数中的 order by 字段不能用 select 中字段的重命名,因为二者是同时执行的。
需求分析 : 先用窗口函数将2019年每个用户的支付总金额算出来并进行排序,再以此作为子查询,从中取出排名在第10、20、30名的用户名、支付总金额以及排名次序。企业一般会使用 dense_rank 进行排序,所以我们这里直接用 dense_rank。
2019年每个用户的支付总金额排名:
2019年支付金额排名在第10、20、30名的用户:
ntile(n) over(partition by …A… order by …B… )
n:切分的片数
A:分组的字段名称
B:排序的字段名称
需求分析 :这个需求很简单,把需求5第一步的排序窗口函数变成切片即可。注意时间筛选条件变成2019年1月。
需求分析 : 排名前10%,也就是一共分成10组,取第1组。那么我们先切片分组:
然后再取第一组:
说明:Lag和Lead分析函数可以在同一次查询中取出同一字段的前N行的数据(Lag)和后N行的数据(Lead)作为独立的列。
在实际应用当中,若要用到取今天和昨天的某字段差值时,Lag和Lead函数的应用就显得尤为重要。当然,这种操作可以用表的自连接实现,但是LAG和LEAD与left join、right join等自连接相比,效率更高,SQL语句更简洁。
lag(exp_str,offset,defval) over(partion by ……order by ……)
lead(exp_str,offset,defval) over(partion by ……order by ……)
lag() 函数示例:
lead() 函数示例:
需求分析: 先要从 user_trade 表中取出每个用户的支付时间,把每个用户放到一个窗口中,按照支付时间进行排序,取出偏移列: lead(dt,1,dt) over(partition by user_name order by dt)。接着基于该子查询,筛选出时间间隔大于100天的用户,并计算数量。
注意 : 如果上面偏移分析函数写成 lead(dt,1,dt) 就不用加后面的 dt is not null 了,因为有默认值的话,间隔就是0,肯定是不满足条件的。
需求分析 :
第一步 :这个需求要用到 user_trade 和 user_info 两张表,前者取支付时间和金额,后者取城市和性别。先对这两张表基于 user_name 进行左连接,并取出相应字段,用窗口函数进行分组排序:
这一步的运行结果是这样的:
第二步 :基于上述结果取出TOP3:
需求分析:
第一步 :这个需求同样要用到两张表 user_refund 和 user_info。我们先把每个退款用户的退款金额和手机品牌取出来,并用窗口函数进行切片排序,25%就是分成4片:
注意 :这里之所以要加 WHERE dt is not null 是因为 user_refund 是一个分区表,分区表要对分区字段进行限制,否则 hive 会报错。
第二步 :选择前25%,也就是第一片:
最后补充一个从 hive 导出结果数据的命令:
以上就是这节课的全部内容了。做完整个练习,真的半条命都没了。窗口函数果然很难,不过掌握方法、多多练习,学会拆解需求,一步一步来做,就能明显降低难度。希望以后有机会能用到这么复杂的技能,哈哈~!
hive not in 通过什么实现
目前hive不支持 in或not in 中包含查询子句的语法,所以只能通过left join实现。
假设有一个登陆表login(当天登陆记录,只有一个uid),和一个用户注册表regusers(当天注册用户,字段只有一个uid),这两个表都包含一个字段,uid。
in查询
如果要查询当天登陆的注册用户,需要用in查询,hive sql如下:
select login.uid from login left outer join regusers on login.uid=regusers.uid where regusers.uid is not null
如果login表和regusers表按天分区,字段是dt,那么查询2013年1月1号当天登陆的注册用户,hive sql如下:
select login.uid from login day_login left outer join
(select uid from regusers where dt=’20130101’) day_regusers
on day_login.uid=day_regusers.uid where day_login.dt=’20130101’ and day_regusers.uid is not null
not in查询
如果要查询当天登陆的老用户(这里假设非当天注册用户就是老用户),需要用not in查询,hive sql如下:
select login.uid from login left outer join regusers on login.uid=regusers.uid where regusers.uid is null;
如果login表和regusers表按天分区,字段是dt,那么查询2013年1月1号当天登陆的老用户,hive sql如下:
select login.uid from login day_login left outer join
(select uid from regusers where dt=’20130101’) day_regusers
on day_login.uid=day_regusers.uid where day_login.dt=’20130101’ and day_regusers.uid is null;
Hive join优化
========================================================
由
于 hive 与传统关系型数据库面对的业务场景及底层技术架构都有着很大差异,因此,传统数据库领域的一些技能放到 Hive 中可能已不再适用。关于
hive 的优化与原理、应用的文章,前面也陆陆续续的介绍了一些,但大多都偏向理论层面,本文就介绍一个实例,从实例中一步步加深对 hive
调优的认识与意识。
1、需求
需求我做了简化,很简单,两张表做个 join,求指定城市,每天的 pv,用传统的 RDBMS SQL 写出来就这样的:
?
1
2
3
4
5
6
7
8
9
10
11
SELECT t.statdate,
c.cname,
count(t.cookieid)
FROM tmpdb.city c
JOIN ecdata.ext_trackflow t ON (t.area1= c.cname
OR t.area2 =c.cname
OR t.area3 = c.cname)
WHERE t.statdate》=’20140818’ and t.statdate《=’20140824’
AND platform=’pc’
GROUP BY t.statdate,
c.cname;
怎么样?根据 SQL 看懂需求没问题吧?
2、非等值 join 问题
然后把这条 SQL 贴到 hive 中去执行,然后你会发现报错了:
?
1
FAILED: SemanticException : Line 5:32 OR not supported in JOIN currently ’cname’
这是因为 hive 受限于 MapReduce 算法模型,只支持 equi-joins(等值 join),要实现上述的非等值 join,你可以采用笛卡儿积( full Cartesian product )来实现:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT t.statdate,
c.cname,
count(t.cookieid)
FROM tmpdb.city c
JOIN ecdata.ext_trackflow t
WHERE t.statdate》=’20140818’
AND t.statdate《=’20140824’
AND platform=’pc’
AND (t.area1= c.cname
OR t.area2 =c.cname
OR t.area3 = c.cname)
GROUP BY t.statdate,
c.cname;
然后再拿着这条语句执行下。
3、优化:reduce side join VS Cartesian product
如果你真的把这条语句放到 Hive 上执行,然后恰好你有张表还非常大,那么恭喜你。。。集群管理员估计会找你的麻烦了。。。
友情提示:笛卡儿积这种语句在 Hive 下慎用,大数据场景下的 m * n 映射结果你懂的。。。对此,Hive 特意提供了一个环境变量:hive.mapred.mode=strict; 防止笛卡儿积的执行:
?
1
FAILED: SemanticException : In strict mode, cartesian product is not allowed. If you really want to perform the operation, set hive.mapred.mode=nonstrict
从 2 中的观察得知我们在 on 后面跟 join
条件,走的是 reduce side join,如果你在 where 后跟则是走 Cartesian product,但是这里单条 sql
又没法实现 reduce side join,还有没有其它办法呢?
4、改写非等值 join:union all
既然不允许非等值 join,那我们换一下思路,多个子查询 union all,然后汇总:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
SELECT dt,
name,
count(cid)
FROM
(SELECT t.statdate dt,
c.cname name,
t.cookieid cid
FROM tmpdb.city c
JOIN ecdata.ext_trackflow t ON t.area1 =c.cname
WHERE t.statdate》=’20140818’
AND t.statdate《=’20140824’
AND platform=’pc’
UNION ALL SELECT t.statdate dt,
c.cname name,
t.cookieid cid
FROM tmpdb.city c
JOIN ecdata.ext_trackflow t ON t.area2 =c.cname
WHERE t.statdate》=’20140818’
AND t.statdate《=’20140824’
AND platform=’pc’
UNION ALL SELECT t.statdate dt,
c.cname name,
t.cookieid cid
FROM tmpdb.city c
JOIN ecdata.ext_trackflow t ON t.area3 =c.cname
WHERE t.statdate》=’20140818’
AND t.statdate《=’20140824’
AND platform=’pc’) tmp_trackflow
GROUP BY dt,
name;
5、优化:map side join
上述语句走的是 reduce side join,从我们的需求及业务得知,tmpdb.city 是一张字典表,数据量很小,因此我们可以试试把上述的语句改写成 mapjoin:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
SELECT dt,
name,
count(cid)
FROM
(SELECT /*+ MAPJOIN(c) */ t.statdate dt,
c.cname name,
t.cookieid cid
FROM tmpdb.city c
JOIN ecdata.ext_trackflow t ON t.area1 =c.cname
WHERE t.statdate》=’20140818’
AND t.statdate《=’20140824’
AND platform=’pc’
UNION ALL SELECT /*+ MAPJOIN(c) */ t.statdate dt,
c.cname name,
t.cookieid cid
FROM tmpdb.city c
JOIN ecdata.ext_trackflow t ON t.area2 =c.cname
WHERE t.statdate》=’20140818’
AND t.statdate《=’20140824’
AND platform=’pc’
UNION ALL SELECT /*+ MAPJOIN(c) */ t.statdate dt,
c.cname name,
t.cookieid cid
FROM tmpdb.city c
JOIN ecdata.ext_trackflow t ON t.area3 =c.cname
WHERE t.statdate》=’20140818’
AND t.statdate《=’20140824’
AND platform=’pc’) tmp_trackflow
GROUP BY dt,
name;
6、优化无极限:开启 parallel 和 控制 reduce 个数
上述语句执行时,你可以看到执行计划和状态信息,以及结合你的 union all 语句可知,三个 union 语句之间没有依赖关系,其实是可以并行执行的:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
explain SQL...
...
STAGE DEPENDENCIES:
Stage-11 is a root stage
Stage-1 depends on stages: Stage-11
Stage-2 depends on stages: Stage-1
Stage-3 depends on stages: Stage-2, Stage-6, Stage-9
Stage-12 is a root stage
Stage-5 depends on stages: Stage-12
Stage-6 depends on stages: Stage-5
Stage-13 is a root stage
Stage-8 depends on stages: Stage-13
Stage-9 depends on stages: Stage-8
Stage-0 is a root stage
...
我们在 SQL 前加上如下环境变量选项:
?
1
2
set mapred.reduce.tasks=60;
set hive.exec.parallel=true;
让执行计划中的 Stage-11、Stage-12、Stage-13 并行执行,并控制好 reduce task 个数。
完整的语句如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
hive -e "
SET mapred.reduce.tasks=60;
SET hive.exec.parallel=TRUE;
SELECT dt,
name,
count(cid)
FROM
(SELECT /*+ MAPJOIN(c) */ t.statdate dt,
c.cname name,
t.cookieid cid
FROM tmpdb.city c
JOIN ecdata.ext_trackflow t ON t.area1 =c.cname
WHERE t.statdate》=’20140818’
AND t.statdate《=’20140824’
AND platform=’pc’
UNION ALL SELECT /*+ MAPJOIN(c) */ t.statdate dt,
c.cname name,
t.cookieid cid
FROM tmpdb.city c
JOIN ecdata.ext_trackflow t ON t.area2 =c.cname
WHERE t.statdate》=’20140818’
AND t.statdate《=’20140824’
AND platform=’pc’
UNION ALL SELECT /*+ MAPJOIN(c) */ t.statdate dt,
c.cname name,
t.cookieid cid
FROM tmpdb.city c
JOIN ecdata.ext_trackflow t ON t.area3 =c.cname
WHERE t.statdate》=’20140818’
AND t.statdate《=’20140824’
AND platform=’pc’) tmp_trackflow
GROUP BY dt,
name;
" 》 a1.txt
最后的优化效果是:2 中的语句三个小时没出结果。。。5 比 4 快 8 倍左右,6 比 5 快 2 倍左右,最终 10min 出结果。

更多文章:
avantage怎么设置为中文(Avantage安装丢失GIT)
2026年10月15日 03:00
activex控件未注册怎么办(登陆视频监控网页时提示activex控件未注册,怎么解决)
2026年10月14日 20:30
excel连接sql数据库教程(excel怎么连接sql数据库)
2026年10月14日 19:40






