Spark2.4新增的函数UDF实践

官网参考:Spark SQL, Built-in Functions

aggregate(array, initialValue, mergeFunc, finishFunc) :从初始值开始,遍历数组中的每个元素进行累积计算。

参数说明:

  • array:需要处理的数组列
  • initialValue:初始值
  • mergeFunc:一个Lambda 函数(acc, element) -> newAcc,用于将当前元素合并到累加器
  • finishFunc(可选):一个Lambda 函数,用于在遍历完所有元素后对最终结果进行一次转换
sql 复制代码
select aggregate(array(1,3,5,7,9),2,(arr,x)->arr+x,arr->arr*10);
>> (2+(1+3+5+7+9))*10=270

filter(array, func) :遍历数组中的每个元素,保留满足Lanbda函数条件的元素,返回一个新的数组

sql 复制代码
-- 保留偶数
select filter(array(1,2,3,4,5), x -> x%2 == 0);
>> 	[2,4]

在spark2.4之前,没有这些高阶函数的话,要么通过explode+collect_list来实现,需要先将数组拆成多行,处理完在重新聚合成数组。这种方式会引入shuffle操作,且无法保证元素顺序。要么就是编写用户自定义函数来实现。

transform(array, func) :使用Lambda函数转换数组中的元素,返回一个长度相同的新数组。

sql 复制代码
-- 单参数形式:只处理元素本身
-- 取array struct中的name元素,返回只有name的新array
select transform(array(named_struct('id', 2, 'name', 'N2'), named_struct('id', 1, 'name', 'N1')), x -> x.name);
>> ["N2","N1"]


-- 双参数形式:可同时使用元素和它的索引,索引从0开始
select transform(array(1,3,5), (x,x_index) -> x+x_index);
>> [1,4,7]

array_sort(array,func):对输入数组进行排序。在spark2.4版本只支持按数组第一个元素进行升序排序。在spark3.1版本才支持自定义排序。

sql 复制代码
-- 对array中的struct进行排序,spark2.4版本默认是按照第一个字段即id进行升序排序,在spark3.1才支持自定义排序
select array_sort(array(named_struct('id', 2, 'name', 'N2'), named_struct('id', 1, 'name', 'N1'))) t;
>> [{"id":1,"name":"N1"},{"id":2,"name":"N2"}]


-- 取array<struct<id:STRING,name:STRING>>中id最小所对应的name
-- 先使用array_sort排序,然后使用transform提取其中的name字段,在使用element_at抓取第一个元素
select element_at(transform(array_sort(array(named_struct('id', 2, 'name', 'N2'), named_struct('id', 1, 'name', 'N1'))),x -> x.name),1);
>> N1

array_distinct(array):从数组中删除重复值。

sql 复制代码
SELECT array_distinct(array(1, 2, 3, null, 3));
>> 	[1,2,3,null]

array_except(array1,array2):返回array1中的元素数组,但不包含array2中的元素,且不包含重复项。即数组差集

sql 复制代码
SELECT array_except(array(1, 2, 3, null, 1), array(1, 3, 5));
>> 	[2,null]

array_interspect(array1,array2):返回array1和array2相交处的元素数组,不包含重复项。即数组交集

sql 复制代码
SELECT array_intersect(array(1, 2, 3, null, 1), array(1, 3, 5));
>> 	[1,3]

array_union(array1,array2):返回array1和array2并集的元素数组,不包含重复项。即数组并集

sql 复制代码
SELECT array_union(array(1, 2, 3, null, 1), array(1, 3, 5));
>> 	[1,2,3,null,5]

array_join(array, delimiter[, nullReplacement]) :将数组中的元素用指定的分隔符连接成一个字符串。其中nullReplacement为可选的,用于替换数组中null值的字符串,如果忽略此参数,null值会被直接过滤掉(不出现,也不占位)

sql 复制代码
SELECT array_join(array('hello', 'world'), ' ');
>> hello world

SELECT array_join(array('hello', null ,'world'), ' ');
>> hello world

SELECT array_join(array('hello', null ,'world'), ' ', ',');
>> hello , world

array_max(array):返回数组中的最大值。对于double/float类型,NaN大于任何非NaN元素。跳过NULL元素。

sql 复制代码
SELECT array_max(array(1, 20, null, 3));
>> 20

SELECT array_max(array('Hi', 'Hello', 'Hio', null));
>> Hio

array_min(array):返回数组中的最小值。对于double/float类型,NaN大于任何非NaN元素。跳过NULL元素。

sql 复制代码
SELECT array_min(array(1, 20, null, 3));
>> 1

array_position(array,element):跳过NULL元素,返回数组中第一个匹配元素的(从1开始的)索引,如果找不到匹配项,则返回0。

sql 复制代码
SELECT array_position(array(1, 3, 5, 1, null), 1);
>> 1

SELECT array_position(array(1, 3, 5, 1, null), 2);
>> 0

SELECT array_position(array(1, 3, 5, 1, null), null);
>> null

array_remove(array,element):从数组中删除所有等于element的元素。

sql 复制代码
select array_remove(array(1,3,1,2,null), 1);
>> 	[3,2,null]

array_repeat(element,count):返回包含element重复count次的数组。

sql 复制代码
select array_repeat('a', 3);
>> 	["a","a","a"]

arrays_overlap(a1, a2):如果a1至少包含一个a2中也存在的非空元素,则返回true。如果数组没有公共元素,并且它们都不是空的,并且其中任何一个都包含空元素,则返回null,否则返回false。

sql 复制代码
SELECT arrays_overlap(array(1, 2, 3, null), array(3, 4, 5));
>> true

SELECT arrays_overlap(array(1, 2, null), array(3, 4, 5));
>> null

SELECT arrays_overlap(array(1, 2), array(3, 4, 5));
>> false

arrays_zip(a1, a2, ...):将多个同索引数组按位置合并为数组结构体(array<struct<0:element1,1:element2,...>>,struct字段名自动为0、1、2....),其中第N个struct包含所有输入数组的第N个值。长度规则:以最长数组为准,短数组缺失位置补NULL。

sql 复制代码
-- 等长数组
select arrays_zip(array(1,2,3), array('a','b','c'));
>> [{"0":1,"1":"a"},{"0":2,"1":"b"},{"0":3,"1":"c"}]

-- 不等长数组
select arrays_zip(array(1,2), array('a','b','c'));
>> [{"0":1,"1":"a"},{"0":2,"1":"b"},{"0":null,"1":"c"}]

可以结合transform对arrays_zip的结果struct进行重命名

sql 复制代码
select transform(arrays_zip(array(1,2,3), array('a','b','c')), x -> struct(x.`0` as id, x.`1` as name));
>> [{"id":1,"name":"a"},{"id":2,"name":"b"},{"id":3,"name":"c"}]

或者可以通过explode来实现

sql 复制代码
select zip_struct.`0` as id, zip_struct.`1` as name
from (
    select explode(arrays_zip(array(1,2,3), array('a','b','c'))) as zip_struct
) as tmp;
>> 
"id"	"name"
"1"	"a"
"2"	"b"
"3"	"c"

element_at(array,index):返回给定(从1开始)索引处的数组元素。如果Index为0,Spark将抛出错误。如果index<0,则从最后一个到第一个访问元素。如果索引超过数组的长度,并且spark.sql.ansi.enabled设置为false,则函数返回NULL。如果spark.sql.ansi.enabled设置为true,则会为无效索引抛出ArrayIndexOutOfBoundsException。

sql 复制代码
select element_at(array(1,2,3), -1);
>> 3

select element_at(array(1,2,3), 0);
>> java.lang.ArrayIndexOutOfBoundsException

select element_at(array(1,2,3), 5);
>> null

array(1,2,3)[0]也可以从array中取值,与element_at不同的是index是从0开始的,而且不支持index<0的操作

element_at(map,key):返回给定键的值。如果映射中不包含键,则函数返回NULL。

sql 复制代码
SELECT element_at(map(1, 'a', 2, 'b'), 2);
>> b

flatten(arrayOfArrays):将一组嵌套数组转换为单个数组,扁平化数组。

sql 复制代码
SELECT flatten(array(array(1, 2), array(3, 4)));
>> [1,2,3,4]

map_concat(map,...):返回所有map的并集

sql 复制代码
SELECT map_concat(map(1, 'a', 2, 'b'), map(3, 'c'));
>> 	{1:"a",2:"b",3:"c"}

map_from_arrays(keys, values):使用一对给定的array创建map。给定的keys中的array的所有元素不能为NULL

sql 复制代码
SELECT map_from_arrays(array('A', 'B'), array('1', '2'));
>> 	{"A":"1","B":"2"}

SELECT map_from_arrays(array('A', 'B', null), array('1', '2', '3'));
>> java.lang.RuntimeException: Cannot use null as map key!

map_from_entries(arrayOfEntries):使用给定的array<struct<...>>来构造map

sql 复制代码
SELECT map_from_entries(array(struct(1, 'a'), struct(2, 'b')));
>> {1:"a",2:"b"}

schema_of_json(json[, options]) :将JSON STRING的内容按照DDL的格式返回

sql 复制代码
SELECT schema_of_json('[{"col":0}]');
>> ARRAY<STRUCT<col: BIGINT>>

shuffle(array):对数组进行随机排列并返回

sql 复制代码
SELECT shuffle(array(1, 20, null, 3));
>> [null,1,3,20]

slice(array, start, length):切割数组。将数组array从start索引开始(数组索引从1开始,如果开始为负,则从末尾开始),取指定长度的数组并返回。

sql 复制代码
SELECT slice(array(1, 2, 3, 4), 2, 2);
>> [2,3]

SELECT slice(array(1, 2, 3, 4), -2, 2);
>> [3,4]

-- 超过长度的也只会取到末尾
SELECT slice(array(1, 2, 3, 4), -1, 2);
>> [4]

weekday(date):返回date/timestamp的星期(0=星期一,1=星期二,...,6=星期日)

sql 复制代码
SELECT weekday('2025-12-31');
>> 2

zip_with(left, right, func):使用Lambda函数将两个给定的数组按元素合并为一个数组。如果一个数组较短,则在应用函数之前,在末尾附加null以匹配较长数组的长度。

sql 复制代码
SELECT zip_with(array(1, 2, 3), array('a', 'b', 'c'), (x, y) -> (y, x));
>> [{"y":"a","x":1},{"y":"b","x":2},{"y":"c","x":3}]

SELECT zip_with(array(1, 2), array(3, 4), (x, y) -> x + y);
>> [4,6]

SELECT zip_with(array('a', 'b', 'c'), array('d', 'e', 'f'), (x, y) -> concat(x, y));
>> ["ad","be","cf"]

与arrays_zip的区别:

arrays_zip仅能合并,返回类型是array<struct>

zip_with可自定义Lambda函数(如相加、拼接等操作),返回类型是array,内部的类型看执行的函数,不一定。

其他复杂函数详解:

explode(array/map):将array或者map炸开成多行。

sql 复制代码
-- 注意:array炸开的字段名默认是col
select explode(array(1, 2, null));
>> 
"col"
"1"
"2"
"NULL"

-- 注意:map炸开的字段名默认是key和value
select explode(map('a',1,'b',2));
>> 
"key"	"value"
"a"	"1"
"b"	"2"

-- 重命名
select explode(array(1, 2, null)) as test_col;
>>
"test_col"
"1"
"2"
"NULL"

select explode(map('a',1,'b',2)) as (k1,v1);
>> 
"k1"	"v1"
"a"	"1"
"b"	"2"

inline(array<struct<...>>) :专门炸开array<struct<...>>的UDF,与explode相比,不仅能炸成多行,还能将struct里面的字段变成多列。

sql 复制代码
-- 注意:炸开的默认字段名是col1,col2...
SELECT inline(array(struct(1, 'a'), struct(2, 'b')));
>> 
"col1"	"col2"
"1"	"a"
"2"	"b"

-- 注意:与explode的区别
select explode(array(struct(1, 'a'), struct(2, 'b')));
>>
"col"
"{""col1"":1,""col2"":""a""}"
"{""col1"":2,""col2"":""b""}"

LATERAL VIEW:用于配合表生成函数(如explode、inline)将一行包含复杂数据类型(如array、map)的数据拆分成多行数据的关键语法,从而实现数据的"行转列"或扁平化处理。具体是把"炸开后的多行"数据和原表的数据关联在一起,相当于对原表的每一行执行一次explode,然后横向拼接。

Lateral View 的基本语法结构为:LATERAL VIEW [OUTER] udtf(expression) virtual_table_alias AS column_alias1, column_alias2...

》》 LATERAL VIEW explode

sql 复制代码
select * 
from (
    select array(1,3,5) as arr, 'N1' as name
    union all 
    select array(2,4,6) as arr, 'N2' as name
) as tmp
LATERAL VIEW explode(arr) t as v
where v < 5;

>> 可以看到v < 5的数据被过滤掉了
"arr"	"name"	"v"
"[1,3,5]"	"N1"	"1"
"[1,3,5]"	"N1"	"3"
"[2,4,6]"	"N2"	"2"
"[2,4,6]"	"N2"	"4"

》》 LATERAL VIEW inline

sql 复制代码
select * 
from (
    select array(struct(1, 'a'), struct(2, 'b')) as arr, 'N1' as name
    union all 
    select array(struct(3, 'c'), struct(4, 'd')) as arr, 'N2' as name
) as tmp
LATERAL VIEW inline(arr) t as test1,test2
;

>> 
"arr"	"name"	"test1"	"test2"
"[{""col1"":1,""col2"":""a""},{""col1"":2,""col2"":""b""}]"	"N1"	"1"	"a"
"[{""col1"":1,""col2"":""a""},{""col1"":2,""col2"":""b""}]"	"N1"	"2"	"b"
"[{""col1"":3,""col2"":""c""},{""col1"":4,""col2"":""d""}]"	"N2"	"3"	"c"
"[{""col1"":3,""col2"":""c""},{""col1"":4,""col2"":""d""}]"	"N2"	"4"	"d"
相关推荐
D愿你归来仍是少年21 小时前
Apache Spark 第 9 章:Spark 性能调优
大数据·spark·apache
Hello.Reader1 天前
Spark 4.0 新特性Python Data Source API 快速上手
python·ajax·spark
墨^O^1 天前
并发控制策略与分布式数据重排:锁机制、Redis 分片与 Spark Shuffle 简析
java·开发语言·c++·学习·spark
Hello.Reader1 天前
Pandas API on Spark 配置选项系统、默认索引与性能调优
大数据·spark·pandas
talen_hx2962 天前
《零基础入门Spark》学习笔记 Day 07
笔记·学习·spark
绿算技术3 天前
OpenClaw × GP Spark:本地智能与极速存储的终极融合
大数据·分布式·spark
Hello.Reader3 天前
Spark Connect 快速入门远程连接 Spark 集群实战
javascript·ajax·spark
Hello.Reader3 天前
Pandas API on Spark 快速入门像写 Pandas 一样使用 Spark
大数据·spark·pandas
talen_hx2964 天前
《零基础入门Spark》学习笔记 Day 06
笔记·学习·spark