本文是一个系列,第一篇打不开了,所以先翻第二篇。
在第一篇讨论了原始类型和嵌入式数据结构,但UDF限制于只有单条输出。
在本篇文章中,我们将关注UDTF,这个函数类型更加复杂,但是支持单个输入输出多行和多列的信息!(继承自org.apache.hadoop.hive.ql.udf.generic.GenericUDTF)
code
本篇文章中所有的代码和数据放在这里:
https://github.com/rathboma/hive-extension-examples
https://github.com/rathboma/hive-extension-examples
事例数据
将用到的表单命名为:people,它只有一行数据:name,每行数据中包含个人或者夫妇的名字。
它存放于文件:people.txt:
~$cat ./people.txt
John Smith
John and Ann White
Ted Green
Dorothy
我们可以将其放到Hadoop上:
hadoop fs -mkdir people
hadoop fs -put ./people.txt people
然后打开hive,创建hive表单
CREATE EXTERNAL TABLE people (name string)
ROW FORMAT DELIMITED FIELDS
TERMINATED BY '\t'
ESCAPED BY ''
LINES TERMINATED BY '\n'
STORED AS TEXTFILE
LOCATION '/user/matthew/people';
UDTF的返回值
上篇所讨论的UDF和GenericUDF方法对单行数据进行操作,它们仅返回一个元素,且必须返回一个值。
这样并不能适用于所有的数据处理任务。由于Hive可以存储多种数据,有时我们并不想一个输入数据只有一行输出,也许我们希望对于每行输入能够有多行输出。例如,想想方法explode(一个 Hive 内部函数) 所能做的事。
相同的,也许我们也希望能够输出多行数据,而不是简单的返回一个值。
这些事我们都可以通过UDTF做到.
一个实际的例子
让我们假设要去创建一个表用于清洗 people的名字,
新的表单将有:
1.若干行的名字和姓(英文名的first name,Surname)。
2.不记录那些没有同时存在姓和名字的数据(有的可能由于没有空格分开,导致姓名和在一起)
3.有的是夫妇的名字,需要分开。
为了达到上述要求,我们将继承接口:
org.apache.hadoop.hive.ql.udf.generic.GenericUDTF
我们需要重写下述三个方法:
// 在该方法中,我们指定输入和输出参数:输入ObjectInspector和输出结构
abstract StructObjectInspector initialize(ObjectInspector[] args) throws UDFArgumentException;
// 在这里,我们处理输入记录,并写出结果记录
abstract void process(Object[] record) throws HiveException;
// 这个函数被调用来通知,UDTF已经没有更多的行来处理。清理代码或额外的输出可以在这里编写。
abstract void close() throws HiveException;
完整的代码
public class NameParserGenericUDTF extends GenericUDTF {
private PrimitiveObjectInspector stringOI = null;
@Override
public StructObjectInspector initialize(ObjectInspector[] args) UDFArgumentException {
if (args.length != 1) {
throw new UDFArgumentException("NameParserGenericUDTF() takes exactly one argument");
}
if (args[0].getCategory() != ObjectInspector.Category.PRIMITIVE
&& ((PrimitiveObjectInspector) args[0]).getPrimitiveCategory() != PrimitiveObjectInspector.PrimitiveCategory.STRING) {
throw new UDFArgumentException("NameParserGenericUDTF() takes a string as a parameter");
}
// input inspectors
stringOI = (PrimitiveObjectInspector) args[0];
// output inspectors -- an object with two fields!
List<String> fieldNames = new ArrayList<String>(2);
List<ObjectInspector> fieldOIs = new ArrayList<ObjectInspector>(2);
fieldNames.add("name");
fieldNames.add("surname");
fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs);
}
public ArrayList<Object[]> processInputRecord(String name){
ArrayList<Object[]> result = new ArrayList<Object[]>();
// ignoring null or empty input
if (name == null || name.isEmpty()) {
return result;
}
String[] tokens = name.split("\\s+");
if (tokens.length == 2){
result.add(new Object[] { tokens[0], tokens[1] });
}else if (tokens.length == 4 && tokens[1].equals("and")){
result.add(new Object[] { tokens[0], tokens[3] });
result.add(new Object[] { tokens[2], tokens[3] });
}
return result;
}
@Override
public void process(Object[] record) throws HiveException {
final String name = stringOI.getPrimitiveJavaObject(record[0]).toString();
ArrayList<Object[]> results = processInputRecord(name);
Iterator<Object[]> it = results.iterator();
while (it.hasNext()){
Object[] r = it.next();
forward(r);
}
}
@Override
public void close() throws HiveException {
// do nothing
}
}
github地址:https://github.com/rathboma/hive-extension-examples
代码运行过程
这个UDTF接收一个String作为输入参数,并返回一个包含两行的数据结构。因为Hive需要知道输入和输出的格式,我们必须手动配置所有的输入和输出对象监察(我实在是不知道怎么形容这个inspector),这点和GenericUDF相似。
我们给输入的String定义PrimitiveObjectInspector:
stringOI = (PrimitiveObjectInspector) args[0]
定义输出对象监察,需要我们来定义列名,并且输出对象监察需要读每一列(在我们的情况下,这两列都是字符串)。
List<String> fieldNames = new ArrayList<String>(2);
fieldNames.add("name");
fieldNames.add("surname");
List<ObjectInspector> fieldOIs = new ArrayList<ObjectInspector>(2);
fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs);
在函数processInputRecord中的逻辑是相当简单的。分离我们的逻辑可以方便于绕开对象监察进行测试。
最后,一旦我们得到了数据结构我们可以调用forward,将得到的数据放入最终将要输出的数据中。
while (it.hasNext()){
Object[] r = it.next();
forward(r);
}
}
使用我们的函数
我们可以编译我们的函数并在HIVE上使用它。
mvn package
cp target/hive-extensions-1.0-SNAPSHOT-jar-with-dependencies.jar ./ext.jar
然后我们可以在hive上使用了
ADD JAR ./ext.jar;
CREATE TEMPORARY FUNCTION process_names as 'com.matthewrathbone.example.NameParserGenericUDTF';
SELECT
adTable.name,
adTable.surname
FROM people
lateral view process_names(name) adTable as name, surname;
得到结果
OK
John Smith
John White
Ann White
Ted Green
测试
最好是将一个UDTF的测试分为两个部分。测试数据处理本身,然后在Hive测试这个函数。在Hive上的测试是必须的,因为在Hive上是相当复杂的,这由不同的元素,输入格式和数据所导致。
下面是分割人的名字为姓名的单元测试,下面的代码可以在这里找到:https://github.com/rathboma/hive-extension-examples
public class NameParserGenericUDTFTest {
@Test
public void testUDTFOneSpace() {
// set up the models we need
NameParserGenericUDTF example = new NameParserGenericUDTF();
ObjectInspector[] inputOI = {PrimitiveObjectInspectorFactory.javaStringObjectInspector};
// create the actual UDF arguments
String name = "John Smith";
// the value exists
try{
example.initialize(inputOI);
}catch(Exception ex){
}
ArrayList<Object[]> results = example.processInputRecord(name);
Assert.assertEquals(1, results.size());
Assert.assertEquals("John", results.get(0)[0]);
Assert.assertEquals("Smith", results.get(0)[1]);
}
}
结束
现在你应该理解这个函数了。
如果你需要更多信息,你可以访问下面地址
http://blog.matthewrathbone.com/2013/08/10/guide-to-writing-hive-udfs.html
https://www.amazon.com/Programming-Hive-Edward-Capriolo/dp/1449319335
本文翻译自:http://beekeeperdata.com/posts/hadoop/2015/07/26/Hive-UDTF-Tutorial.html