我有一个名为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"))
也许......这篇文章相关/有用。
答案 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部分new
和old
创建数据帧。每个,选择爆炸&#39; trackingEvents&#39;部分的专栏。将这些val
数据框声明保存为newTE
和oldTE
。
创建另一个数据框,其中挑选的列对oldTrackingEvents
和newTrackingEvents
数组中的每个跟踪事件都是唯一的,例如每个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
- 我不确定?