常常会有类似这样的需求:
数据是这样的
user_id int 用户ID
pay_channel int 充值渠道ID
pay_cents int 充值金额
user_id+pay_channel为唯一键
求每个用户充值金额最多的渠道。
直观上有两种思路:
1. 先求每个用户在所有渠道上的最大充值,然后回表join
select t1.user_id , t1.pay_channel
from table t1 join
(
select user_id , max(pay_cents) max_pay from table group by user_id
) t2 on (t1.user_id = t2.user_id and t1.pay_cents = t2.max_pay) ;
2.用先分析函数对pay_cent做排名标记,再用排名来过滤
select user_id , pay_channel from
(
select
user_id , pay_channel , rank(pay_cents)over)(partition by user_id ) rk
from table
) t1
where rk = 1 ;
上面两个方法都需要两个MR来完成。
根据Hive的特性,我们可以有第三种思路,通过UDAF用一个MR完成。
select user_id , getKeyWithMaxValue(pay_channel , pay_cents) max_channels
from table
group by user_id ;
getKeyWithMaxValue(key , value)是我自定义的UDAF,用来求最大value所对应的key,这样的key可能是多个,所以返回值是list.
数据是这样的
user_id int 用户ID
pay_channel int 充值渠道ID
pay_cents int 充值金额
user_id+pay_channel为唯一键
求每个用户充值金额最多的渠道。
直观上有两种思路:
1. 先求每个用户在所有渠道上的最大充值,然后回表join
select t1.user_id , t1.pay_channel
from table t1 join
(
select user_id , max(pay_cents) max_pay from table group by user_id
) t2 on (t1.user_id = t2.user_id and t1.pay_cents = t2.max_pay) ;
2.用先分析函数对pay_cent做排名标记,再用排名来过滤
select user_id , pay_channel from
(
select
user_id , pay_channel , rank(pay_cents)over)(partition by user_id ) rk
from table
) t1
where rk = 1 ;
上面两个方法都需要两个MR来完成。
根据Hive的特性,我们可以有第三种思路,通过UDAF用一个MR完成。
select user_id , getKeyWithMaxValue(pay_channel , pay_cents) max_channels
from table
group by user_id ;
getKeyWithMaxValue(key , value)是我自定义的UDAF,用来求最大value所对应的key,这样的key可能是多个,所以返回值是list.
UDAF实现代码如下:
public class GetKeyWithMaxValue extends UDAF {
public static class GetKeyWithMaxValue_int_int implements UDAFEvaluator{
//最终结果
private HashMap<Integer , Integer > result;
//负责初始化计算函数并设置它的内部状态,result是存放最终结果的
@Override
public void init() {
result= new HashMap<Integer , Integer >() ;
}
//每次对一个新值进行聚集计算都会调用iterate方法
public boolean iterate(Integer key , Integer value)
{
if (result == null )
result=new HashMap<Integer , Integer >();
if(result.containsKey(key))
{
int new_value = result.get(key) ;
if (new_value < value)
new_value = value ;
result.put(key , new_value) ;
}
else
result.put( key , value ) ;
return true;
}
//Hive需要部分聚集结果的时候会调用该方法
//会返回一个封装了聚集计算当前状态的对象
public HashMap<Integer , Integer > terminatePartial()
{
return result;
}
//合并两个部分聚集值会调用这个方法
public boolean merge(HashMap<Integer , Integer > other)
{
//将 result和other合并
Iterator<Entry<Integer, Integer>> iter = other.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Integer , Integer> entry = iter.next();
int key = entry.getKey();
int val = entry.getValue();
if(result.containsKey(key))
{
int new_value = result.get(key) ;
if (new_value < val)
new_value = val ;
result.put(key , new_value) ;
}
else
result.put( key , val ) ;
}
return true ;
}
//Hive需要最终聚集结果时候会调用该方法
public ArrayList<Integer> terminate()
{
ArrayList<Integer> res = new ArrayList<Integer>() ;
//先找出最大的value
int max_value = Integer.MIN_VALUE ;
Iterator<Entry<Integer, Integer>> iter = result.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Integer , Integer> entry = iter.next();
int val = entry.getValue();
if (val > max_value)
max_value = val ;
}
Iterator<Entry<Integer, Integer>> iter2 = result.entrySet().iterator();
while (iter2.hasNext()) {
Map.Entry<Integer , Integer> entry = iter2.next();
int key = entry.getKey();
int val = entry.getValue();
if (val == max_value)
res.add(key) ;
}
return res ;
}
}
}