MaxCompute中的UDT(User Defined Type)功能支持在SQL中直接引用第三方语言的类或者对象,获取其数据内容或者调用其方法 。
在其他的SQL引擎中也有UDT的概念,但是和MaxCompute的概念有许多差异。很多SQL引擎中的概念比较像MaxCompute的struct复杂类型。而某些语言提供了调用第三方库的功能,如Oracle 的 CREATE TYPE。相比之下,MaxCompute的UDT更像这种CREATE TYPE的概念,Type中不仅仅包含数据域,还包含方法。而且MaxCompute做的更彻底:开发者不需要用特殊的DDL语法来定义类型的映射,而是在SQL中直接使用。
一个简单的例子如下:
set odps.sql.type.system.odps2=true; -- 打开新类型,因为下面的操作会用到 Integer,即 int类型
SELECT java.lang.Integer.MAX_VALUE;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o6DlMiy6-1577083585688)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fkipUBLr-1577083585689)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
上面的例子输出:
+-----------+
| max_value |
+-----------+
| 2147483647 |
+-----------+
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XodFDzEP-1577083585690)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S0o5mAjo-1577083585690)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
和java语言一样,java.lang这个package是可以省略的。所以上面例子更可以简写为:
set odps.sql.type.system.odps2=true;
SELECT Integer.MAX_VALUE;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yJWJhTDj-1577083585691)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-219Re2tM-1577083585691)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
可以看到,上面的例子在select列表中直接写上了类似于java表达式的表达式,而这个表达式的确就按照java的语义来执行了。这个例子表现出来的能力就是MaxCompute的UDT。
UDT所提供的所有扩展能力,实际上用UDF都可以实现。譬如上面的例子,如果使用UDF实现,需要做下列操作。
首先,定义一个UDF的类:
package com.aliyun.odps.test;
public class IntegerMaxValue extends com.aliyun.odps.udf.UDF {
public Integer evaluate() {
return Integer.MAX_VALUE;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-beJg1DcY-1577083585692)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XVVtaoAR-1577083585693)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
然后,将上面的UDF编译,并打成jar包。然后再上传jar包,并创建function
add jar odps-test.jar;
create function integer_max_value as 'com.aliyun.odps.test.IntegerMaxValue' using 'odps-test.jar';
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WL45hijy-1577083585693)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TmPLLsbc-1577083585693)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
最后才可以在sql中使用
select integer_max_value();
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rXhrvQXT-1577083585694)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rsf5RjHu-1577083585694)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
UDT相当于简化了上述一系列的过程,让开发者能够轻松简单地用其他语言扩展SQL的功能。
上述例子表现的是java静态域访问的能力,而UDT的能力远不限于此。譬如下面的例子:
-- 示例数据
@table1 := select * from values ('100000000000000000000') as t(x);
@table2 := select * from values (100L) as t(y);
-- 代码逻辑
@a := select new java.math.BigInteger(x) x from @table1; -- new创建对象
@b := select java.math.BigInteger.valueOf(y) y from @table2; -- 静态方法调用
select /*+mapjoin(b)*/ x.add(y).toString() from @a a join @b b; -- 实例方法调用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KycEbw9A-1577083585696)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T6agt9wz-1577083585696)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
上述例子输出结果 100000000000000000100。
这个例子还表现了一种用UDF比较不好实现的功能:子查询的结果允许UDT类型的列。例如上面变量a的x列是java.math.BigInteger类型,而不是内置类型。UDT类型的数据可以被带到下一个operator中再调用其他方法,甚至能参与数据shuffle。比如上面的例子,在MaxCompute studio中的执行图如下:
可以看出图中共有三个STAGE: M1, R2 和 J3。熟悉MapReduce原理的用户会知道,由于join的存在需要做数据reshuffle,所以会出现多个stage。一般情况下,不同stage不仅是在不同进程,甚至是在不同物理机器上运行的。双击代表M1的方块,显示如下:
可以看到,M1仅仅执行了 new java.math.BigInteger(x) 这个操作。而同样点开代表J3的方块,可以看到 J3 在不同的阶段执行了 java.math.BigInteger.valueOf(y) 的操作,和 x.add(y).toString() 的操作:
这几个操作不仅仅是分阶段执行的,甚至是在不同进程,不同物理机器上执行的。但是UDT把这个过程封装起来,让用户看起来和在同一个JVM中执行的效果几乎一样。
UDT同样允许用户上传自己的jar包,并且直接引用。如上面UDF的jar包。用UDT来使用:
set odps.sql.type.system.odps2=true;
set odps.sql.session.resources=odps-test.jar; --指定要引用的jar,这些jar一定要事先上传到project,并且需要是jar类型的资源
select new com.aliyun.odps.test.IntegerMaxValue().evaluate();
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tae4RqCS-1577083585698)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TZ76ROIu-1577083585699)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
如果觉得写 package全路径麻烦,还可以像java的import一样,用flag来指定默认的package。
set odps.sql.type.system.odps2=true;
set odps.sql.session.resources=odps-test.jar;
set odps.sql.session.java.imports=com.aliyun.odps.test.*; -- 指定默认的package
select new IntegerMaxValue().evaluate();
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OxYTOnlo-1577083585699)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bm90ZKyz-1577083585700)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
目前UDT 只支持java语言。
提供一些提升使用效率的flag:
set odps.sql.session.resources=foo.sh,bar.txt;
注意这个flag和SELECT TRANSFORM中指定资源的flag相同,所以这个flag会同时影响SELECT TRANSFORM和UDT两个功能。*
。暂不支持static import。UDT支持的操作包括:
实例化对象的new操作。
实例化数组的new操作,包括使用初始化列表创建数组,如new Integer[] { 1, 2, 3 }
。
方法调用,包括静态方法调用(因此能用工厂方法构建对象).
域访问,包括静态域。
注意:
Java SDK 的类都是默认可用的。但是需要注意目前runtime使用的JDK版本是JDK1.8,比该版本更新的JDK功能可能不支持。
需要特别注意的是, 所有的运算符都是MaxCompute SQL的语义,不是UDT的语义 。如 String.valueOf(1) + String.valueOf(2)
的结果是 3 (string隐式转换为double,并且double相加),而不是’12’ (java中string相加是concatenate的语义)。
除了string的相加操作比较容易混淆外,另一个比较容易混淆的是 =
操作。SQL中的=
不是赋值 而是判断相等。而对于java对象来说,判断相等应该用equals方法,通过等号判断的相等无法保证其行为(在UDT场景下,同一对象的概念是不能保证的,具体原因参考下述第8点)。
内置类型与特定java类型有一一映射关系,见UDF类型映射。这个映射在UDT也有效:
'123'.length() , 1L.hashCode()
。chr(Long.valueOf('100'))
,其中 Long.valueOf 返回的是 java.lang.Long 类型的数据,而内置函数chr接受的数据类型是内置类型BIGINT。set odps.sql.type.system.odps2=true;
才能使用的。否则会报错。UDT对泛型有比较完整的支持,如 java.util.Arrays.asList(new java.math.BigInteger('1'))
,编译器能够根据参数类型知道该方法的返回值是 java.util.List
类型
注意构造函数需要指定类型参数,否则使用java.lang.Object
,这一点和java保持一致:
new java.util.ArrayList(java.util.Arrays.asList('1', '2'))
的结果是 java.util.ArrayList
类型;
而 new java.util.ArrayList
的结果是 java.util.ArrayList
类型。
UDT对 “同一对象” 的概念是模糊的。这是由数据的reshuffle导致的。从上面第一部分的join的示例可以看出,对象有可能会在不同进程,不同物理机器之间传输,在传输过程中同一个对象的两个引用后面可能分别引用了不同的对象(比如对象先被shuffle到两台机器,然后下次又shuffle回一起)。
= operator
来判断相等,而是使用 equals
方法。目前UDT不能用作shuffle key:包括join,group by,distribute by,sort by, order by, cluster by 等结构的key
并不是说UDT不能用在这些结构里面,UDT可以在expression中间的任意阶段使用,只是不能作为最终输出。比如虽然不能 group by new java.math.BigInteger('123')
,但是可以 group by new java.math.BigInteger('123').hashCode()
。因为hashCode的返回值是int.class类型可以当做内置类型int来使用(应上述“内置类型与特定java类型映射”的规则)。
注意:这个限制未来的版本会计划去掉。
UDT扩展了类型转换规则:
目前UDT对象不能落盘。这意味着不能将UDT对象insert到表中(实际上DDL不支持UDT,创建不出来这样的表),当然,隐式类型转换变成了内置类型的除外。同时,屏显的最终结果也不能是UDT类型,对于屏显的场景,由于所有的java类都有toString()方法,而java.lang.String
类型是合法的。所以debug的时候,可以用这种方法来观察UDT的内容。
set odps.sql.udt.display.tostring=true;
这样MaxCompute会自动把所有的以UDT为最终输出的列wrap上 java.util.Objects.toString(...)
,从而方便调试。这个flag只对屏显语句生效,对insert语句不生效,所以专门用在调试中。UDT不仅能够实现scalar函数的功能,配合着内置函数collect_list和explode(doc),完全能够实现 aggregator和table function的功能。
set odps.sql.type.system.odps2=true;
set odps.sql.udt.display.tostring=true;
select
new Integer[10], -- 创建一个10个元素的数组
new Integer[] {c1, c2, c3}, -- 通过初始化列表创建一个长度为3的数组
new Integer[][] { new Integer[] {c1, c2}, new Integer[] {c3, c4} }, -- 创建多维数组
new Integer[] {c1, c2, c3} [2], -- 通过下标操作访问数组元素
java.util.Arrays.asList(c1, c2, c3); -- 这个创建了一个 List,这个也能当做array来用,所以这是另一个创建内置array数据的方法
from values (1,2,3,4) as t(c1, c2, c3, c4);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1OpXkMgW-1577083585700)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e3Jr09XX-1577083585701)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
UDT的runtime自带一个gson的依赖(2.2.4)。因此用户可以直接使用gson
set odps.sql.type.system.odps2=true;
set odps.sql.session.java.imports=java.util.*,java,com.google.gson.*; -- 同时import多个package,用逗号隔开
@a := select new Gson() gson; -- 构建gson对象
select
gson.toJson(new ArrayList(Arrays.asList(1, 2, 3))), -- 将任意对象转成 json 字符串
cast(gson.fromJson('["a","b","c"]', List.class) as array) -- 反序列化json字符串, 注意gson的接口,直接反序列化出来是List
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v273x6Lo-1577083585704)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2LnsetqN-1577083585704)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
相比于get_json_object,上述用法不仅仅是使用方便了,在需要对json字符串多个部分做内容提取时,先将gson字符串反序列成格式化数据,其效率要高得多。
除了GSON, MaxCompute runtime自带的依赖还包括: commons-logging(1.1.1), commons-lang(2.5), commons-io(2.4),protobuf-java(2.4.1)。
内置类型array和map 与 java.util.List 和 java.util.Map 存在映射关系。结果就是:
set odps.sql.type.system.odps2=true;
set odps.sql.session.java.imports=java.util.*;
select
size(new ArrayList()), -- 对 ArrayList数据调用内置函数size
array(1,2,3).size(), -- 对内置类型array调用 List的方法
sort_array(new ArrayList()), -- 对 ArrayList 的数据进行排序
al[1], -- 虽然java的List不支持下标操作,但是别忘了array是支持的
Objects.toString(a)), -- 过去不支持将array类型cast成string,现在有绕过方法了
array(1,2,3).subList(1, 2) -- 求subList
from (select new ArrayList(array(1,2,3)) as al, array(1,2,3) as a) t;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tZ6dVDY6-1577083585705)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YDtZpDBU-1577083585705)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
还可以实现一些特殊的功能,比如 array的distinct
select cast (new java.util.ArrayList(new java.util.HashSet(array(1L, 2L, 2L))) as array); -- 输出 [1, 2]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e4JptnFY-1577083585706)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-foHgPUXP-1577083585706)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
UDT实现聚合的原理是,先用COLLECT_SET 或 COLLECT_LIST 函数将数据转变成 List, 之后对该List应用UDT的标量方法求得这一组数据的聚合值。
如用下面的示例实现对BigInteger求中位数(由于数据是 java.math.BigInteger类型的,所以不能直接用内置的median函数)
set odps.sql.session.java.imports=java.math.*;
@test_data := select * from values (1),(2),(3),(5) as t(value);
@a := select collect_list(new BigInteger(value)) values from @test_data; -- 先把数据聚合成list
@b := select sort_array(values) as values, values.size() cnt from @a; -- 求中位数的逻辑,先将数据排序
@c := select if(cnt % 2 == 1, new BigDecimal(values[cnt div 2]), new BigDecimal(values[cnt div 2 - 1].add(values[cnt div 2])).divide(new BigDecimal(2))) med from @b;
-- 最终结果
select med.toString() from @c;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gSkXgCDS-1577083585707)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-synKlGnD-1577083585707)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
由于collect_list会先把所有数据都收集到一块,是没有办法实现partial aggregate的,所以这个做法的效率会比内置的aggregator或者udaf低,所以 在内置aggregator能实现的情况下,应尽量使用内置的aggregator 。同时把一个group的所有数据都收集到一起的做法,会增加数据倾斜的风险。
但是另一方面,如果UDAF本身的逻辑就是要将所有数据收集到一块(比如类似wm_concat的功能),此时使用上述方法,反而可能比UDAF(注意不是内置aggregator)高。
表值函数允许输入多行多列数据,输出多行多列数据。可以按照下述原理实现:
下述示例实现将一个json字符串的内容展开出来的功能
@a := select '[{"a":"1","b":"2"},{"a":"1","b":"2"}]' str; -- 示例数据
@b := select new com.google.gson.Gson().fromJson(str, java.util.List.class) l from @a; -- 反序列化json
@c := select cast(e as java.util.Map
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7plXcdes-1577083585708)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-egdaOrxc-1577083585709)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
我们知道在UDF中可以通过ExecutionContext对象来读取资源文件。现在UDT也可以通过 com.aliyun.odps.udt.UDTExecutionContext.get()
方法来或者这样的一个 ExecutionContext 对象。
下述示例将资源文件 1.txt 读取到一个string对象中,并输出:
set odps.sql.session.resources=1.txt;
select new String(com.aliyun.odps.udt.UDTExecutionContext.get().readResourceFile('1.txt')) text;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7OwtVNsZ-1577083585709)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1uYWWvun-1577083585710)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
UDT对象默认是不支持落盘的。但是有方法能够把UDT的对象持久化。基本的思想是将数据序列化成为binary或者string来做持久化,或者将udt对象展开,持久化里面的能转成内置类型的关键数据。
如下UDT定义:
public class Shape
{
public List points;
public Shape(List points)
{
this.points = points;
}
}
public class Point
{
public int x;
public int y;
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oYnqcLwl-1577083585710)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oCjiuPfg-1577083585711)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
将对象展开成内置类型:
@data := select key, shape from ...;
@exploded := select key, point from @data lateral view explode(shape.points) t as point;
@expanded := select key, point.x, point.y from @exploded;
insert into table points select * from @expanded;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uWXmK25K-1577083585712)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O9YmRK4U-1577083585712)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
需要用时再重新构造:
select key, new Shape(collect_list(new Point(x, y))) as shape from points group by key;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U0rZcat9-1577083585715)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nHBryB7z-1577083585716)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
或者将对象serialize成binary。
平展开的最大问题是,序列化和反序列化的麻烦。当然可以直接转成binary。如改造Shape类:
-- 改造 Shape 类
public com.aliyun.odps.data.Binary serialize() {
ByteBuffer buffer = ByteBuffer.allocate(points.size() * 8 + 4);
buffer.putInt(points.size());
for (Point point : points) {
buffer.putInt(point.x);
buffer.putInt(point.y);
}
return new com.aliyun.odps.data.Binary(buffer.array());
}
public static Shape deserialize(com.aliyun.odps.data.Binary bytes) {
ByteBuffer buffer = ByteBuffer.wrap(bytes.data());
int size = buffer.getInt();
List points = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
points.add(new Point(buffer.getInt(), buffer.getInt()));
}
return new Shape(points);
}
-- 需要持久化的时候,调用serialize()
select key, shape.serialize() data from ...
-- 需要读取的时候,调用deserialize方法
select key, Shape.deserialize(data) as Shape from ...
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ALlOscBF-1577083585716)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9hzmuVgE-1577083585717)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
如果直接利用已有的框架,也许会更方便。如 Shape 是用 ProtoBuffer 定义的
-- shape 的定义
message Point
{
required int32 x = 1;
required int32 y = 2;
}
message Shape
{
repeated Point points = 1;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M8GE1Kni-1577083585717)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l0qyYQEG-1577083585718)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
SQL中直接调用pb的方法
select key, new com.aliyun.odps.data.Binary(shape.toByteArray()) from ...
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WSpocsWJ-1577083585718)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a6VaUJ8F-1577083585718)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
本功能和 MaxCompute Studio 搭配着使用,才能发挥其最大的价值。
功能方面,UDT的优势是显而易见的:
在性能方面,UDT执行过程和UDF非常接近,其性能与UDF几乎是一致的,而且产品针对UDT做了很多优化,在某些场景下UDT的性能甚至略高一筹:
values[x].add(values[y]).divide(java.math.BigInteger.valueOf(2))
这个看似存在多次UDT方法调用的操作,实际上只有一次调用。所以虽然UDT操作的单元都比较小,但是并不会因此造成多次函数调用的接口上的额外开销。在安全控制方面,UDT和UDF完全一样。即都会受到沙箱policy的限制。所以如果要使用受限的操作,需要打开沙箱隔离,或者申请沙箱白名单。
本文从使用的角度介绍了UDT的功能。UDT能够在SQL中直接写java的表达式,并可以引用jdk中的类。这一功能极大地方便扩展SQL的功能。
当然,UDT的功能还有许多功能还有待完善。文中也提到了几点有待完善的功能:
原文链接
本文为阿里云内容,未经允许不得转载。