Hadoop Hive UDTF教程

本文介绍Hadoop Hive中的UDTF,它允许单个输入产生多行多列的输出。通过一个实例,展示了如何创建一个UDTF用于拆分和清洗名字,包括处理夫妇名字,以及编译和在Hive上测试该函数的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文是一个系列,第一篇打不开了,所以先翻第二篇。
在第一篇讨论了原始类型和嵌入式数据结构,但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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值