我有DataFrame
名为链接的Row
中包含动态数量的字段/列。
但是,某些字段的结构 [ClassName] Id 包含ID
[ClassName] Id 的类型始终为String
我有几个Datasets
,每种类型 [ClassName]
每个Dataset
至少包含字段id
(String
)和typeName
(String
),它们始终填充{{1}的字符串值}
e.g。如果我有3 [ClassName]
类型 A , B 和 C
链接:
DataSets
答
+----+-----+-----+-----+
| id | AId | BId | CId |
+----+-----+-----+-----+
| XX | A01 | B02 | C04 |
| XY | null| B05 | C07 |
B:
+-----+----------+-----+-----+
| id | typeName | ... | ... |
+-----+----------+-----+-----+
| A01 | A | ... | ... |
首选的最终结果是链接 +-----+----------+-----+-----+
| id | typeName | ... | ... |
+-----+----------+-----+-----+
| B02 | B | ... | ... |
,其中每个Id都被替换或附加一个名为Dataframe
的字段,并封装了原始对象。
结果:
[ClassName]
我尝试过的事情
+----+----------------+----------------+----------------+
| id | A | B | C |
+----+----------------+----------------+----------------+
| XX | A(A01, A, ...) | B(B02, B, ...) | C(C04, C, ...) |
| XY | null | B(B05, B, ...) | C(C07, C, ...) |
,其中第一个元素是原始Row
,第二个元素是匹配的Row
但是,第二次迭代开始嵌套这些结果。
尝试使用map“取消”这些结果要么导致编码器地狱(因为结果[ClassName]
不是固定类型),或者编码太复杂而导致催化剂error 欢迎任何想法。
答案 0 :(得分:0)
所以我想出了如何做我想做的事。 我做了一些改动让它为我工作,但它是一个 为了参考目的,我将展示我的步骤,也许它对未来的某个人有用吗?
case class Base(id: String, typeName: String)
case class A(override val id: String, override val typeName: String) extends Base(id, typeName)
Dataframe
val linkDataFrame = spark.read.parquet("[path]")
DataFrame
转换为可连接的内容,这意味着为已加入的源创建占位符,并将所有单个Id
字段(AId,BId等)转换为{ {1}}来源 - > ID的。 Spark有一个有用的sql map
方法。我们还需要将Map
类转换为Base
以便在编码器中使用。尝试了多种方式,但无法绕过具体的声明(否则会出错)StructType
val linkDataFrame = spark.read.parquet("[path]")
case class LinkReformatted(ids: Map[String, Long], sources: Map[String, Base])
// Maps each column ending with Id into a Map of (columnname1 (-Id), value1, columnname2 (-Id), value2)
val mapper = linkDataFrame.columns.toList
.filter(
_.matches("(?i).*Id$")
)
.flatMap(
c => List(lit(c.replaceAll("(?i)Id$", "")), col(c))
)
val baseStructType = ScalaReflection.schemaFor[Base].dataType.asInstanceOf[StructType]
,其ID在一个字段中名为 ids ,而来源的占位符在空DataFrame
Map[String, Base]
val linkDatasetReformatted = linkDataFrame.select(
map(mapper: _*).alias("ids")
)
.withColumn("sources", lit(null).cast(MapType(StringType, baseStructType)))
.as[LinkReformatted]
(A,B等)加入此重新格式化的链接数据集。这种尾部递归方法中发生了很多事情Datasets
@tailrec
def recursiveJoinBases(sourceDataset: Dataset[LinkReformatted], datasets: List[Dataset[Base]]): Dataset[LinkReformatted] = datasets match {
case Nil => sourceDataset // Nothing left to join, return it
case baseDataset :: remainingDatasets => {
val typeName = baseDataset.head.typeName // extract the type from base (each field hase same value)
val masterName = "source" // something to name the source
val joinedDataset = sourceDataset.as(masterName) // joining source
.joinWith(
baseDataset.as(typeName), // with a base A,B, etc
col(s"$typeName.id") === col(s"$masterName.ids.$typeName"), // join on source.ids.[typeName]
"left_outer"
)
.map {
case (source, base) => {
val newSources = if (source.sources == null) Map(typeName -> base) else source.sources + (typeName -> base) // append or create map of sources
source.copy(sources = newSources)
}
}
.as[LinkReformatted]
recursiveJoinBases(joinedDataset, remainingDatasets)
}
}
个Dataset
条记录,其中 ID 字段中的每个对应LinkReformatted
都是相应的typeName -> id
个来源字段。
对我来说就够了。我可以通过最终的数据集我希望这有点帮助。我理解这不是我所询问的确切解决方案,也不是非常简单。