UDF连接行对象中隐藏的未定义案例类的数组

时间:2017-11-13 17:12:57

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

我有一个名为sessions的数据框,其列可能会随时间而变化。 (编辑以澄清:我没有列的案例类 - 只有一个反映的架构。)我将在外部作用域中始终有一个uuid和clientId,其中包含一些其他内部和外部作用域列可能构成跟踪事件,所以...类似于:

root
 |-- runtimestamp: long (nullable = true)
 |-- clientId: long (nullable = true)
 |-- uuid: string (nullable = true)
 |-- oldTrackingEvents: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- timestamp: long (nullable = true)
 |    |    |-- actionid: integer (nullable = true)
 |    |    |-- actiontype: string (nullable = true)
 |    |    |-- <tbd ... maps, arrays and other stuff matches sibling> section
 ...
 |-- newTrackingEvents: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- timestamp: long (nullable = true)
 |    |    |-- actionid: integer (nullable = true)
 |    |    |-- actiontype: string (nullable = true)
 |    |    |-- <tbd ... maps, arrays and other stuff matches sibling>      
 ...

我现在要将oldTrackingEvents和newTrackingEvents与包含这些参数和尚未解析的代码逻辑的UDF合并:

val mergeTEs = udf((oldTEs : Seq[Row], newTEs :  Seq[Row]) =>
        // do some stuff - figure best way 
        //    - to merge both groups of tracking events
        //    - remove duplicates tracker events structures
        //    - limit total tracking events < 500 
        return result // same type as UDF input params
    )

UDF返回结果将是结构的数组,该结构是连接的两个字段的结果列表。

问题: 我的问题是如何构建这样的UDF - (1)使用正确的传入参数类型,(2)在UDF中操作这些集合的方法和(3)返回不具有的值的明确方法编译器错误。我没有成功测试Seq[Row]输入/输出(使用val testUDF = udf((trackingEvents : Seq[Row]) => trackingEvents)并收到错误java.lang.UnsupportedOperationException: Schema for type org.apache.spark.sql.Row is not supported以直接返回trackingEvents。但是,我没有收到错误返回{{ 1}}而不是Some(1) ...操作集合的最佳方法是什么,这样我就可以使用评论部分中的活动将上述模式建议的2个相同结构列表与UDF连接起来。目标是使用此操作:

trackingEvents

并且在每一行中......以内存/速度有效的方式返回单个“trackingEvents”结构数组。

SUPPLEMENTAL:

查看向我显示的问题......如果存在相关性,可能会有提示...... Defining a UDF that accepts an Array of objects in a Spark DataFrame? ... sessions.select(mergeTEs('oldTrackingEvents, 'newTrackingEvents).as("cleanTrackingEvents")) 也许......这篇文章相关/有用。

2 个答案:

答案 0 :(得分:1)

我认为question you've linked解释了这一切,所以重申一下。使用udf时:

  • StructType的输入表示是弱类型Row对象。
  • StructType的输出类型必须为Scala Product。您无法返回Row对象。

如果这是一个很大的负担,你应该使用强类型Dataset

val f: T => U 
sessions.as[T].map(f): Dataset[U]

其中T是代表Session架构的代数数据类型,U是代表结果的代数数据类型。

答案 1 :(得分:0)

或者 ...如果您的目标是将某些随机行结构/模式的序列与某些操作合并,这是一种替代的通常声明的方法,可以避免分区讨论:

从主数据框中,为每个trackingEvents部分newold创建数据帧。每个,选择爆炸&#39; trackingEvents&#39;部分的专栏。将这些val数据框声明保存为newTEoldTE

创建另一个数据框,其中挑选的列对oldTrackingEventsnewTrackingEvents数组中的每个跟踪事件都是唯一的,例如每个uuid,{{1}和事件clientId。您的伪架构将是:

timestamp

使用UDF加入结构的两个简单序列,(uuid: String, clientId : Long, newTE : Seq[Long], oldTE : Seq[Long]),这类似于未经测试的&#39;例如:

Seq[Long]

UDF将返回已清理跟踪事件的数据框。您现在拥有一个非常纤薄的数据框,其中包含已删除的事件,可以在联合回到彼此之后自行加入到展开的val limitEventsUDF = udf { (newTE: Seq[Long], oldTE: Seq[Long], limit: Int, tooOld: Long) => { (newTE ++ oldTE).filter(_ > tooOld).sortWith(_ > _).distinct.take(limit) }} newTE框架中。

此后根据需要使用collect_list进行GroupBy。

仍然......这似乎很多工作 - 这应该被投票为oldTE - 我不确定?