1. hive0.10及之前的版本没有row_number这个函数,假设我们现在出现如下业务场景,现在我们在hdfs上有个log日志文件,为了方便叙述,该文件只有2个字段,第一个是用户的id,第二个是当天登录的timestamp,现在我们需要求每个用户最早登录的那条记录(注意不是仅仅只要那个登录的timestamp),可以方便计算NewUser。
2. 我们的数据是这样的:
1,32
2,46
3,312
4,4643
5,54
6,456
7,437
8,5347
9,47
1,466
2,546
3,4
4,886
5,546
6,57
7,235
8,765
9,634
这里是假设的数据。
3.我们可以用hive建立一张表,其中第一个字段是id string, 第二个是login_time bigint,假设我们的表名是log。
4. 这样的场景可以用很多方法解决,但是我们可以用RowNumber函数,在0.11及以上的版本才集成到了hive中,但是我们公司用的是CDH4.5.0,hive才到0.10,所以只能自己写个这样的函数,具体的代码如下:
- import org.apache.hadoop.hive.ql.exec.UDF;
- public class RowN extends UDF {
- private static int MAX_VALUE = 50;
- private static String comparedColumn[] = new String[MAX_VALUE];
- private static int rowNum = 1;
- public int evaluate(Object... args) {
- String columnValue[] = new String[args.length];
- for (int i = 0; i < args.length; i++) {
- columnValue[i] = args[i].toString();
- }
- if (rowNum == 1) {
- for (int i = 0; i < columnValue.length; i++)
- comparedColumn[i] = columnValue[i];
- }
- for (int i = 0; i < columnValue.length; i++) {
- if (!comparedColumn[i].equals(columnValue[i])) {
- for (int j = 0; j < columnValue.length; j++) {
- comparedColumn[j] = columnValue[j];
- }
- rowNum = 1;
- return rowNum++;
- }
- }
- return rowNum++;
- }
- }
- </span>
5. 稍微解释下这个UDF,首先我们的UDF函数输入是多个列的值,传入多个值表示用多个值是否相同来打序号,对于我们的场景只要1个(就是id),函数row_number(),必须带一个或者多个列参数,如ROW_NUMBER(col1, ....),它的作用是按指定的列进行分组生成行序列。在ROW_NUMBER(a,b) 时,若两条记录的a,b列相同,则行序列+1,否则重新计数。
6. 接下去关键的就是怎么取使用这个函数了,我们必须保证查出来的数据是有序的,这样才好加序号,而且要根据某个字段排序,但是如果数据量大或者我们自己设置了多个reducer咋办,这样的话我们就想到了使用distribute by和sort by的配合使用,可以使key相同的数据进入同一个reducer,这样就好办了,那么我们的hql语句其实就是一句话:
- create temporary function RowNumber as 'xxx.xxx.xxx.udf.RowNumber';
- select id, login_time from (select * from log distribute by id sort by id, login_time asc) tmp where RowNumber(id)=1;
132
2 46
3 4
4 886
5 54
6 25
7 75
8 534
9 47
注:
1. 如果对distribute by不熟悉可以看另一个我的博客,有具体的解释:http://blog.csdn.net/jthink_/article/details/38903775
2. 这个函数最关键的部分就是得先有序,所以加序号前必须保证数据有序