处理嵌套数据使用高阶函数在SQL数据砖
2017年5月24日 在公司博客上
嵌套数据类型提供砖客户和Apache引发用户的强大处理结构化数据的方法。特别是,他们允许你把复杂的对象和数组一样,地图和内部结构的列。这可以帮助你模型数据在一个更自然的方式。虽然这个功能肯定是有用的,但它会很麻烦操纵数据的内部复杂的对象,因为SQL(火花)没有原语来处理这些数据。此外,它是耗时的,non-performant和非平凡的。
由于这些原因,我们兴奋地提供高阶函数的SQL砖运行时的3.0版本,允许用户高效地创建函数,在SQL操作基于数组的数据。高阶函数是一个简单的扩展SQL操作嵌套数组等数据。例如,变换
下面表达式显示了如何添加一个数字数组中每个元素:
在这篇文章中,我们将讨论以前的方法嵌套SQL数据操作,其次是高阶函数语法我们已经介绍了砖。
过去的方法
在我们介绍数组操作的新语法之前,让我们首先讨论当前操作这类数据在SQL方法:
- 内置函数(有限的功能)
- 解压缩数组为单个行,应用你的函数,然后重新打包成数组(很多步骤,因此效率低下)
- udf(不是一般的或有效)
我们将探讨这些独立,这样你可以理解为什么数组操作是很困难的。让我们开始下面的表的模式(包括看到笔记本容易运行的代码)。
根| - - -关键:长(可空=假)|——价值观:数组(可空=假)| |——元素:整数(containsNull =真正的)|——nested_values:数组(可空=假)| |——元素:数组(containsNull =假)| | |——元素:整数(containsNull =真正的)
内置函数
火花SQL确实有一些内置函数用于操作数组。例如,您可以创建一个数组,它的大小,得到特定元素,检查如果数组包含一个对象,数组进行排序。SQL还支持发电机(火花爆炸
,pos_explode
和内联
),允许您将输入行数组元素,和collect_list
聚合。这个功能可以满足您的需要对某些任务,但它是复杂的做任何事不平凡的,如计算每个数组元素的一个定制的表达。
解包和重新打包
非平凡的操作的常用的方法是“打开和重新打包”方法。这是一个“火花SQL原生”的方式解决问题,因为你不需要编写任何自定义代码;您可以简单地编写SQL代码。打开和重新打包方式是通过以下步骤:
- 使用
侧面图爆炸
平数组,输入行与数组中的每个元素相结合; - 应用一个给定的变换,在这个例子中
值+ 1
爆炸数组中的每个元素;和 - 使用
collect_list
或collect_set
创建一个新的数组。
我们可以看到一个这样的例子在SQL代码如下:
选择键,值,collect_list (价值+1)作为values_plus_one从nested_data横向视图爆炸(值)T作为价值集团通过键,值
虽然这种方法肯定奏效,它有一些问题。首先,你必须绝对确保密钥用于分组是独一无二的,否则结果将是不正确的。第二,没有保证排序数组的火花SQL。指定一个操作,需要一个特定的顺序几乎保证不正确的结果。最后,生成的火花SQL计划可能会非常昂贵。
用户定义的函数(udf)
最后,我们可以编写自定义udf来操作数组的数据。udf必须定义如何遍历一个数组,我们如何处理单个元素。让我们看看一些基本例子在Python和Scala。
(code_tabs)
从pyspark.sql.types进口IntegerType从pyspark.sql.types进口ArrayTypedefadd_one_to_els(元素):返回[el +1为埃尔在元素)spark.udf.register (“plusOneIntPython”、add_one_to_els ArrayType (IntegerType ()))
defaddOneToElements(元素:Seq [Int])=元素。地图(= >元素+1)火花。udf。注册(“plusOneInt”addOneToElements (_:Seq [Int]):Seq [Int])
[/ code_tabs]
一旦注册,我们可以使用这些函数来操纵我们的数据在火花SQL。
选择键,值,plusOneInt (值)作为values_plus_one,plusOneIntPython (值)作为values_plus_one_py从nested_data
这种方法有一些优势在过去的版本:例如,它维护元素顺序,与包装和重新打包方法。然而,它有两个主要缺点。首先,你必须编写函数在其他语言比SQL和注册之前运行。第二,数据序列化到Scala和Python可以非常昂贵,减缓udf火花的SQL内置的优化处理。
我们的方法:高阶函数
从上面的例子中,观察到传统的方式在SQL繁琐操作嵌套数据。为此,我们建立了一个简单的解决方案在砖:高阶函数的SQL。
我们的解决方案引入了两个SQL函数编程结构:高阶函数和匿名(λ)函数。这些共同允许您定义的函数操作数组在SQL。的高阶函数,如变换
,和一个数组lambda函数从用户运行。然后调用这个lambda函数数组中的每个元素。
一个简单的例子:变换
让我们说明前面的概念与我们之前的转换的例子。在这种情况下,高阶函数,变换
将遍历该数组,相关的lambda函数适用于每一个元素,并创建一个新数组。lambda函数,元素+ 1
,指定每个元素是如何被操纵的。
选择键,值,变换(值,价值- - - - - ->价值+1)作为values_plus_one从nested_data
清晰的说明,转换变换(价值观、价值- >价值+ 1)
有两个组件:
变换值. .)
是高阶函数。这需要一个数组和一个匿名函数作为输入。内部变换将照顾设置一个新数组,每个元素应用匿名函数,分配结果输出数组。- 的
值- >值+ 1
是一个匿名函数。的功能分为两个组件分离- >
符号:
一个。参数列表。在这种情况下,我们只有一个论点:价值。我们也支持多个参数通过创建一个逗号分隔的参数列表包围括号,例如:(x, y) - > x + y
。
b。身体。这是一个表达式,可以使用参数和外部变量计算新值。在这种情况下,我们的值加1的论点。
捕获变量
我们还可以使用其他比lambda函数中的参数变量;这就是所谓的捕捉。我们可以用变量定义在顶层,或在中间lambda函数定义的变量。例如,下面的改变增加了关键(顶级)变量值数组中的每个元素:
选择键,值,变换(值,价值- - - - - ->价值+键)作为values_plus_key从nested_data
嵌套调用
有时数据是深度嵌套。如果你想改变这样的数据,你可以可以使用嵌套的lambda函数。下面的例子将整数数组,数组并添加关键(顶级)列和中间数组的大小嵌套数组中的每个元素。
选择键,nested_values,变换(nested_values值- - - - - ->变换(值,价值- - - - - ->价值+关键+大小(值)))作为new_nested_values从nested_data
支持功能
我们有下面的高阶函数添加到3.0版本的砖运行时。
变换(数组
、功能
):数组
这产生一个数组
通过应用函数
一个输入的每个元素数组
。
注意,函数式编程操作映射。这已经被转换为了防止混乱的地图表达(从一个键值表达式创建一个地图)。
下面的查询转换的值数组每个元素通过添加键值:
选择键,值,变换(值,价值- - - - - ->价值+键)transformed_values从nested_data
存在(数组
、功能
):布尔
返回true,如果谓词函数
适用于任何元素输入数组
。
下面的示例检查数组包含一个元素的值模10 = 1:
选择键,值,存在(值,价值- - - - - ->价值%10==1)filtered_values从nested_data
过滤器(数组
、功能
):数组
生成一个输出数组
从输入数组
只有只有谓词添加元素函数
成立。
下面的例子过滤器与一个值只数组元素的值> 50允许:
选择键,值,过滤器(值,价值- - - - - ->价值>50)filtered_values从nested_data
总(数组
B函数、功能R):
减少的元素数组
成一个单一的值R
通过合并元素到一个缓冲区B
使用函数
并通过应用finish函数
在最后一个缓冲区。初始值B
是由一个零的表达式。完成的功能是可选的,如果你不指定函数确定函数恒等函数(id - > id)
使用。
这是唯一的高阶函数,取两个lambda函数。
下面的例子总结(骨料)数组的值到一个(总和)值。两个版本完成函数(summed_values
)和一个没有完成的功能summed_values_simple
显示:
选择键,值,减少(值,0,(价值,acc)- - - - - ->价值+acc, acc- - - - - ->acc) summed_values,减少(值,0,(价值,acc)- - - - - ->价值+acc) summed_values_simple从nested_data
你也可以计算更复杂的聚合。下面的代码显示了计算几何平均数的数组元素。
选择键,值,聚合(值,(1.0作为产品,0作为N),(缓冲区,价值)- - - - - ->(价值*缓冲区。产品,buffer.N+1),缓冲- - - - - ->权力(buffer.product1.0/buffer.N) geomean从nested_data
结论
高阶函数将可用砖3.0运行时。如果你有任何嵌套的数据,一定要试一试!