Spark连接数据帧&数据集

时间:2017-06-01 13:38:35

标签: apache-spark apache-spark-sql rdd apache-spark-dataset

我有DataFrame名为链接Row中包含动态数量的字段/列。 但是,某些字段的结构 [ClassName] Id 包含ID

[ClassName] Id 的类型始终为String

我有几个Datasets,每种类型 [ClassName] 每个Dataset至少包含字段idString)和typeNameString),它们始终填充{{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]

我尝试过的事情

  • 对joinWith的递归调用。 第一个调用成功返回元组/ +----+----------------+----------------+----------------+ | 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
  • 以RDD身份加入尚无法解决此问题。

欢迎任何想法。

1 个答案:

答案 0 :(得分:0)

所以我想出了如何做我想做的事。 我做了一些改动让它为我工作,但它是一个 为了参考目的,我将展示我的步骤,也许它对未来的某个人有用吗?

  1. 首先,我声明一个数据类型,它共享我感兴趣的 A,B,C 等所有属性,并使类从这个超类型扩展
  2. case class Base(id: String, typeName: String)
    case class A(override val id: String, override val typeName: String) extends Base(id, typeName)
    
    1. 接下来,我加载了链接Dataframe
    2. val linkDataFrame = spark.read.parquet("[path]")
      
      1. 我希望将此DataFrame转换为可连接的内容,这意味着为已加入的源创建占位符,并将所有单个Id字段(AId,BId等)转换为{ {1}}来源 - > ID的。 Spark有一个有用的sql map方法。我们还需要将Map类转换为Base以便在编码器中使用。尝试了多种方式,但无法绕过具体的声明(否则会出错)
      2. StructType
        1. 所有这些部分都可以创建一个新的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
        2. Map[String, Base]
          1. 下一步是将所有来源val linkDatasetReformatted = linkDataFrame.select( map(mapper: _*).alias("ids") ) .withColumn("sources", lit(null).cast(MapType(StringType, baseStructType))) .as[LinkReformatted] (A,B等)加入此重新格式化的链接数据集。这种尾部递归方法中发生了很多事情
          2. Datasets
            1. 您现在最终得到@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来源字段。 对我来说就够了。我可以通过最终的数据集
            2. 使用一些地图函数来提取我需要的一切

              我希望这有点帮助。我理解这不是我所询问的确切解决方案,也不是非常简单。