同上篇Hadoop MapReduce 实现分组Top n介绍一样,这次学习Hive实现分组Top n。
在数据处理中,经常会碰到这样一个场景,对表数据按照某一字段分组,然后找出各自组内最大的几条记录情形。针对这种分组Top N问题,我们利用Hive、MapReduce等多种工具实现一下。
对类如下users表记录,取出不同grade下得分最多的两条记录
id grade score
1 A 10
2 A 40
3 B 30
4 C 20
5 B 10
6 D 40
7 A 30
8 C 20
9 B 10
10 D 40
11 C 30
12 D 20
先对users根据grade进行分区,然后根据score进行倒序排序,再应用row_number函数从每个分区中筛选出各个分区的前两条记录。
WITH groupsort AS (
SELECT *, rid = ROW_NUMBER() OVER
(PARTITION BY grade ORDER BY score DESC) FROM users
)
SELECT id, grade, score FROM groupsort WHERE rid <= 2;
这里使用WITH AS抽离子查询部分,相当于定义一个SQL片断,该SQL片断会被整个SQL语句所用到。有的时候,是为了让SQL语句的可读性、优化性更高些,因为如果WITHAS短语所定义的表名被调用两次以上,优化器会自动将WITH AS短语所获取的数据放入一个TEMP表里,如果只是被调用一次,则不会。
然后使用row_number() over(partition by clo1 order by clo2 desc) 方法来对clo1分组并且计算同组clo2最大的一条或几条记录。
我们知道Hive允许执行HQL语句,虽说HQL和SQL有很多相同之处,但还是有许多差别之处,以ROW_NUMBER()为例:
在Hive低版本中,ROW_NUMBER()在HIVE中是一个函数,必须带一个或者多个列参数,如ROW_NUMBER(col1, ....),它的作用是按指定的列进行分组生成行序列,在ROW_NUMBER(a,b) 时,若两条记录的a,b列相同,则行序列+1,否则重新计数。而在高版本中就取消了这一限制,写法和现在的sql一致,同时,因为HIVE是基于MAPREADUCE的,必须保证ROW_NUMBER执行是在REDUCE中,并且ROW_NUMBER中使用的列中,列值相同的记录要再同一个reduce中,否则ROW_NUMBER的行为是无意义的。
将目标数据存入临时表中
drop table tmp_users_time;
create table tmp_users_time
as
select * from
(
select u.*,row_numwer() over(distribute by grade sort by score desc) sn
from users u
)tu
where tu.sn > 2;
或者将数据添加到记录时刻top的大表中,默认大表自动添加时间
insert into table users_time_top
select tu.grade,tu.score
from
(
select u.*,row_number() over(distribute by grade sort by score desc) sn
from users u
)tu
where tu.sn > 2;
select u.*,row_number(u.score) as sn
from
(
select grade,score from users distribute by grade sort by score desc
)u
where row_number(score) > 2;
这里有两点需要说明:
使用子查询保证ROW_NUMBER在reduce端执行。
使用distribute by grade sort by score desc来保证grade相同的记录被分配到相同的REDUCE中。
今天也学习了一种使用Hive udf函数实现Top n的方法,先对grade和score排序,然后对grade计数,在where条件过滤出grade列计数器大于n的行即可。
(1)定义UDF:
package com.example.hive.udf;
import org.apache.hadoop.hive.ql.exec.UDF;
public final class Rank extends UDF{
private int counter;
private String last_key;
public int evaluate(final String key){
if (!key.equalsIgnoreCase(this.last_key) ) {
this.counter = 0;
this.last_key = key;
}
return this.counter++;
}
}
(2)注册jar、建表、导数据,查询:
add jar Rank.jar;
create temporary function rank as 'com.example.hive.udf.Rank';
create table users(id int,grade string,score int) row format delimited fields terminated by ' ';
LOAD DATA LOCAL INPATH 'users.txt' OVERWRITE INTO TABLE users;
select grade,score from (
select grade,rank(grade) cnum,score from (
select id, grade, score from users distribute by grade sort by score desc
)u
)b where cnum < 2;
这种是临时转换UDF,永久的需要单独设置。
了解更多推荐:
http://blog.sina.com.cn/s/blog_5ceb51480102wabj.html
http://chiyx.iteye.com/blog/1559460