Apache spark 火花连接数据帧&;数据集
我有一个名为链接的Apache spark 火花连接数据帧&;数据集,apache-spark,apache-spark-sql,rdd,apache-spark-dataset,Apache Spark,Apache Spark Sql,Rdd,Apache Spark Dataset,我有一个名为链接的DataFrame,在行中有一个动态的字段/列。 但是,有些字段具有包含Id的结构[ClassName]Id [ClassName]Id的类型始终为String 我有两个不同类型的数据集,[ClassName] 每个数据集至少有字段id(String)和typeName(String),这些字段总是用[ClassName]的字符串值填充 e、 g.如果我有3个数据集类型A、B和C 链接: +----+-----+-----+-----+ |id |援助|投标| CId| +--
DataFrame
,在行中有一个动态的字段/列。
但是,有些字段具有包含Id的结构[ClassName]Id
[ClassName]Id的类型始终为String
我有两个不同类型的数据集,[ClassName]
每个数据集
至少有字段id
(String
)和typeName
(String
),这些字段总是用[ClassName]
的字符串值填充
e、 g.如果我有3个数据集类型A、B和C
链接:
+----+-----+-----+-----+
|id |援助|投标| CId|
+----+-----+-----+-----+
|XX | A01 | B02 | C04|
|XY | null | B05 | C07 |
A:
+-----+----------+-----+-----+
|id | typeName |…||
+-----+----------+-----+-----+
|A01 | A |……||代码>
B:
+-----+----------+-----+-----+
|id | typeName |…||
+-----+----------+-----+-----+
|B02 | B |…||代码>
首选的最终结果是链接数据帧
,其中每个Id要么替换,要么附加一个名为[ClassName]
的字段,并封装原始对象
结果:
+----+----------------+----------------+----------------+
|id | A | B | C|
+----+----------------+----------------+----------------+
|XX | A(A01,A,…)| B(B02,B,…)| C(C04,C,…)|
|XY | null | B(B05,B,…)| C(C07,C,…)|
我尝试过的事情
- 递归调用joinWith。
第一个调用成功返回一个元组/
行
,其中第一个元素是原始的行
,第二个元素是匹配的[ClassName]
然而,第二次迭代开始嵌套这些结果。
尝试使用map“unnest”这些结果可能会导致编码器地狱(因为生成的行
不是固定类型),或者编码非常复杂,导致催化剂失效
- 以RDD身份加入还无法解决此问题
欢迎任何想法。所以我想出了如何做我想做的事。
我做了一些改变,让它为我工作,但它是一个
作为参考,我将展示我的步骤,也许它对将来的人有用
首先,我声明一个数据类型,它共享我感兴趣的a、B、C等的所有属性,并使类从这个超类型扩展
case类基(id:String,typeName:String)
案例类A(覆盖val id:String,覆盖val typeName:String)扩展了基(id,typeName)
接下来,我加载链接Dataframe
val linkDataFrame=spark.read.parquet(“[path]”)
我想将这个DataFrame
转换成可连接的东西,这意味着为连接的源创建一个占位符,并将所有单个Id
字段(AId、BId等)转换为源->Id的映射。Spark有一个有用的方法。我们还需要将Base
类转换为StructType
,以便在编码器中使用。尝试了多种方法,但无法绕过特定的声明(否则将引发错误)
val linkDataFrame=spark.read.parquet(“[path]”)
案例类链接重新格式化(ID:Map[String,Long],sources:Map[String,Base])
//将以Id结尾的每列映射到(columnname1(-Id)、value1、columnname2(-Id)、value2)的映射中
val mapper=linkDataFrame.columns.toList
.过滤器(
_.matches(“(?i)。*Id$”)
)
.平面图(
c=>List(lit(c.replaceAll(“(?i)Id$”,“”)),col(c))
)
val baseStructType=ScalaReflection.schemaFor[Base].dataType.asInstanceOf[StructType]
所有这些部分都可以创建一个新的数据框
,其中Id的全集字段名为ids,并在一个空的映射[String,Base]
中为源创建一个占位符
val linkdatasetreformated=linkDataFrame.select(
映射(映射器:*)。别名(“ID”)
)
.withColumn(“sources”,lit(null).cast(映射类型(StringType,baseStructType)))
.as[LinkReformatted]
下一步是将所有源数据集
(A、B等)连接到此重新格式化的链接数据集。在这个递归方法中发生了很多事情
@tailrec
def recursiveJoinBases(源数据集:数据集[LinkReformatted],数据集:列表[Dataset[Base]]):数据集[LinkReformatted]=数据集匹配{
case Nil=>sourceDataset//没有剩余的连接,返回它
case baseDataset::remainingDatasets=>{
val typeName=baseDataset.head.typeName//从基中提取类型(每个字段具有相同的值)
val masterName=“source”//something来命名源
val joinedDataset=sourceDataset.as(masterName)//加入源
.joinWith(
baseDataset.as(typeName),//带基数a、B等
col(s“$typeName.id”)==col(s“$masterName.id.$typeName”),//在source.id上联接。[typeName]
“左外”
)
.地图{
案例(来源、基础)=>{
val newSources=if(source.sources==null)映射(typeName->base)else source.sources+(typeName->base)//追加或创建源映射
source.copy(sources=newSources)
}
}
.as[LinkReformatted]
递归JoinBase(joinedDataset,剩余数据集)
}
}
现在,您将得到一个数据集
的链接重新格式化
记录,其中ids字段中每个对应的typeName->id
都有一个对应的typeName->