我的数据框 df1 包含150列和多行。我还有一个数据框 df2 具有相同的架构,但很少有行包含应该应用于 df1 的编辑(其中'一个关键列 id 识别要更新的行。 df2 只包含已填充更新的列。其他列为null。我想要做的是使用以下方式从dataframe df2 更新 df1 中的行来更新 df1 中的行:
我怎样才能做到最好?是否可以通用的方式完成而不列出所有列,而是迭代它们?可以使用dataframe API完成,还是需要切换到RDD?
(当然,通过更新数据帧df1,我的意思是创建一个新的,更新的数据帧。)
让我们说架构是:id:Int,name:String,age:Int。
df1 是:
1,"Greg",18
2,"Kate",25
3,"Chris",30
df2 是:
1,"Gregory",null
2,~,26
更新的数据框应如下所示:
1,"Gregory",18
2,null,26
3,"Chris",30
答案 0 :(得分:0)
我想出了如何通过中间转换到RDD来实现它。首先,创建一个映射idsToEdits,其中键是行ID,值是列号到值的映射(只有非空值)。
val idsToEdits=df2.rdd.map{row=>
(row(0),
row.getValuesMap[AnyVal](row.schema.fieldNames.filterNot(colName=>row.isNullAt(row.fieldIndex(colName))))
.map{case (k,v)=> (row.fieldIndex(k),if(v=="~") null else v)} )
}.collectAsMap()
Broadast,用于映射和定义更新行的editRow函数。
val idsToEditsBr=sc.broadcast(idsToEdits)
import org.apache.spark.sql.Row
val editRow:Row=>Row={ row =>
idsToEditsBr
.value
.get(row(0))
.map{edits => Row.fromSeq(edits.foldLeft(row.toSeq){case (rowSeq,
(idx,newValue))=>rowSeq.updated(idx,newValue)})}
.getOrElse(row)
}
最后,在源自df1的RDD上使用该函数并转换回数据帧。
val updatedDF=spark.createDataFrame(df1.rdd.map(editRow),df1.schema)
答案 1 :(得分:0)
听起来你的问题是如何在没有明确命名所有列的情况下执行此操作,因此我假设你有一些" doLogic" udf函数或数据框函数,用于在加入后执行逻辑。
import org.apache.spark.sql.types.StringType
val cols = df1.schema.filterNot(x => x.name == "id").map({ x =>
if (x.dataType == StringType) {
doLogicUdf(col(x), col(x + "2")))
} else {
when(col(x + "2").isNotNull, col(x + "2")).otherwise(col(x))
}
}) :+ col("id")
val df2 = df2.select(df2.columns.map( x=> col(x).alias(x+"2")) : _*))
df1.join(df2, col("id") ===col("id2") , "inner").select(cols : _*)
答案 2 :(得分:0)
您也可以使用case或使用完全外部联接合并两个数据框。请参阅下面的链接以获取解释。 Spark incremental loading overwrite old record