我正在尝试使用spark 1.5.2
处理在移动应用中收到的JSON事件(例如点击等)。有多个应用程序版本,事件的结构因版本而异。
假设版本1具有以下结构:
{
"timestamp": "",
"ev": {
"app": {
"appName": "XYZ",
"appVersion": "1.2.0"
}
"device": {
"deviceId": "ABC",
...
}
...
}
}
另一个版本具有以下结构:
{
"timestamp": "",
"ev": {
"_a": {
"name": "XYZ",
"version": "1.3.0"
}
"_d": {
"androidId": "ABC",
...
}
...
}
}
我希望能够为结构创建单个数据帧并执行一些查询。
我使用filter
函数为每个结构创建两个不同的数据帧。现在我需要能够重命名列以对两个数据帧执行联合操作。
我正在使用:
df.withColumnRenamed("ev.app", "ev._a").withColumnRenamed("ev.device", "ev._d");
但这不起作用。我如何实现这一目标?
答案 0 :(得分:6)
如果只是重命名嵌套列而不是更改架构结构,那么替换DataFrame架构(使用新架构重新创建DataFrame)就可以了。
object functions {
private def processField(structField: StructField, fullColName: String, oldColName: String, newColName: String): StructField = {
if (fullColName.equals(oldColName)) {
new StructField(newColName, structField.dataType, structField.nullable)
} else if (oldColName.startsWith(fullColName)) {
new StructField(structField.name, processType(structField.dataType, fullColName, oldColName, newColName), structField.nullable)
} else {
structField
}
}
private def processType(dataType: DataType, fullColName: String, oldColName: String, newColName: String): DataType = {
dataType match {
case structType: StructType =>
new StructType(structType.fields.map(
f => processField(f, if (fullColName == null) f.name else s"${fullColName}.${f.name}", oldColName, newColName)))
case other => other
}
}
implicit class ExtDataFrame(df: DataFrame) {
def renameNestedColumn(oldColName: String, newColName: String): DataFrame = {
df.sqlContext.createDataFrame(df.rdd, processType(df.schema, null, oldColName, newColName).asInstanceOf[StructType])
}
}
}
用法:
scala> import functions._
import functions._
scala> df.printSchema
root
|-- geo_info: struct (nullable = true)
| |-- city: string (nullable = true)
| |-- country_code: string (nullable = true)
| |-- state: string (nullable = true)
| |-- region: string (nullable = true)
scala> df.renameNestedColumn("geo_info.country_code", "country").printSchema
root
|-- geo_info: struct (nullable = true)
| |-- city: string (nullable = true)
| |-- country: string (nullable = true)
| |-- state: string (nullable = true)
| |-- region: string (nullable = true)
这个实现是递归的,所以它也应该处理这样的情况:
df.renameNestedColumn("a.b.c.d.e.f", "bla")
答案 1 :(得分:2)
给出两条消息M1
和M2
case class Ev1(app1: String)
case class M1(ts: String, ev1: Ev1)
case class Ev2(app2: String)
case class M2(ts: String, ev2: Ev2)
和两个数据帧df1
(包含M1
)和df2
(包含M2
),两个数据帧都注册为临时表,那么你可以使用QL :
val merged = sqlContext.sql(
"""
|select
| df1.ts as ts,
| named_struct('app', df1.ev1.app1) as ev
| from
| df1
|
|union all
|
|select
| df2.ts as ts,
| named_struct('app', df2.ev2.app2) as ev
| from
| df2
""".stripMargin)
as
提供相同的名称named_struct
即时构建兼容的嵌套结构union all
将所有内容放在一起示例中未显示,但collect_list
等函数也可能有用。