分组Top N问题(三) - sql及Hive实现

前言:

同上篇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

1、普通SQL实现:

先对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最大的一条或几条记录。

2、Hive利用row_number实现

我们知道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的行为是无意义的。

高版本写法:(通常hive会将row_number生成的伪列写入到新表中,以便拉取各单元各时刻的top n,如微博各小时热搜榜)

将目标数据存入临时表中

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中。

UDF函数实现:

今天也学习了一种使用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


你可能感兴趣的:(项目及框架总结,hive,top,n,udf函数,数据)