如何使用相当复杂的模式映射数据集?

时间:2017-09-26 07:51:03

标签: apache-spark dataframe apache-spark-sql

我正在使用Dataframe,它具有与此类似的复杂模式:

 root
 |-- NPAData: struct (nullable = true)
 |    |-- NPADetails: struct (nullable = true)
 |    |    |-- location: string (nullable = true)
 |    |    |-- manager: string (nullable = true)
 |    |-- usersDetails: array (nullable = true)
 |    |    |-- element: struct (containsNull = true)
 |    |    |    |-- name: string (nullable = true)
 |    |    |    |-- contacts: array (nullable = true)
 |    |    |    |    |-- element: struct (containsNull = true)
 |    |    |    |    |    |-- phone: string (nullable = true)
 |    |    |    |    |    |-- email: string (nullable = true)
 |    |    |    |    |    |-- address: string (nullable = true)
 |    |-- service: array (nullable = true)
 |    |    |-- element: struct (containsNull = true)
 |    |    |    |-- serviceName: string (nullable = true)
 |    |    |    |-- serviceCode: string (nullable = true) 
 |-- NPAHeader: struct (nullable = true)
 |    |    |-- code: string (nullable = true)
 |    |    |-- date: string (nullable = true)

我想执行一个地图,在DataFrame的每一行应用自定义函数以满足要求:

  

数据框的每一行都有2个或更多元素,这些元素具有我在问题中发布的结构。首先,我想在行列表中分隔每行的这些元素,因为我需要比较它们。一个我有一个DataFrame [List [Row]]我想应用另一个地图,所以我可以合并每个列表的元素(为此我有一个递归函数我写的检查列表中的顺序并填充新的空字段具有较旧值的元素)。在我使用RDD完成所有这些操作之前,我正在尝试使用DataFrame API

我认为我需要传递编码器。

由于模式相当复杂(至少我不知道如何在Array中存在哪些元素也是Arrays时生成StructType)我尝试的是通过传递模式生成编码器,执行以下操作:

import org.apache.spark.sql.catalyst.encoders.RowEncoder

val sourceSchema = dfSoruce.schema 

val encoder = RowEncoder(sourceSchema)

dfSoruce.map(x => x.getList[Row](0))(encoder)

但是我收到以下错误:

  

类型不匹配;发现:   org.apache.spark.sql.catalyst.encoders.ExpressionEncoder [org.apache.spark.sql.Row]   需要:   org.apache.spark.sql.Encoder [java.util.List的[org.apache.spark.sql.Row]]

如何将ExpressionEncoder转换为编码器?

1 个答案:

答案 0 :(得分:1)

  

我想执行一个地图,在DataFrame的每一行应用自定义函数,但为此我需要传递一个编码器。

让我不同意。

map Operator(要避免)

引用map运营商的scaladoc

  

map [U](func:(T)⇒U)(隐式arg0:编码器[U]):数据集[U] 返回一个新的数据集,其中包含将func应用于每个的结果元件。

您可能已经注意到编码器(在第二个参数列表中)是一个隐式参数,因此不必明确提供(这就是Scala中隐含的美妙, isn&#39是吗?

我的建议是使用func转换为可编码类型U,即您可以在数据集中使用的任何类型。您可以在Encoders对象中找到可将类型转换为可编码变体的可用编码器。

scala> :type ids
org.apache.spark.sql.Dataset[Long]

scala> ids.map(id => (id, "hello" * id.toInt)).show(truncate = false)
+---+---------------------------------------------+
|_1 |_2                                           |
+---+---------------------------------------------+
|0  |                                             |
|1  |hello                                        |
|2  |hellohello                                   |
|3  |hellohellohello                              |
|4  |hellohellohellohello                         |
|5  |hellohellohellohellohello                    |
|6  |hellohellohellohellohellohello               |
|7  |hellohellohellohellohellohellohello          |
|8  |hellohellohellohellohellohellohellohello     |
|9  |hellohellohellohellohellohellohellohellohello|
+---+---------------------------------------------+

但是,只有在map和标准功能不足之后,我才会提前withColumn进行更高级的转换。

(推荐)withColumn Operator和Standard Functions

我宁愿使用withColumn运算符与functions对象中的标准函数一起使用map - 就像行为一样。

让我们回顾一下您的要求,看看我们采用这种方法的程度。

  

首先,我想分隔行列表中每行的元素

对我来说,行列表听起来像groupBy聚合,后跟collect_list函数(可能有一些withColumn运算符来提取所需的值)。

// leave you to fill the gaps
dfSoruce.withColumn(...).groupBy(...).agg(collect_list(...))

你不必考虑编码器(鉴于它们在Spark SQL中相当低级且相当先进的概念)