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个
数据集
类型ABC

链接:

+----+-----+-----+-----+
|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->