Scala 如何根据以前记录的值更新spark dataframe的列

Scala 如何根据以前记录的值更新spark dataframe的列,scala,apache-spark,apache-spark-sql,spark-dataframe,Scala,Apache Spark,Apache Spark Sql,Spark Dataframe,我在df中有三列 Col1,col2,col3 X,x1,x2 Z,z1,z2 Y, X,x3,x4 P,p1,p2 Q,q1,q2 Y 我想做以下几件事 当col1=x时,存储col2和col3的值 当col1=y时,将这些列值分配给下一行 预期产量 X,x1,x2 Z,z1,z2 Y,x1,x2 X,x3,x4 P,p1,p2 Q,q1,q2 Y,x3,x4 任何帮助都将不胜感激 注意:-spark 1.6是,有一个功能需要订购 import org.apach

我在df中有三列

Col1,col2,col3

X,x1,x2

Z,z1,z2

Y,

X,x3,x4

P,p1,p2

Q,q1,q2

Y
我想做以下几件事 当col1=x时,存储col2和col3的值 当col1=y时,将这些列值分配给下一行 预期产量

X,x1,x2

Z,z1,z2

Y,x1,x2

X,x3,x4

P,p1,p2

Q,q1,q2

Y,x3,x4
任何帮助都将不胜感激 注意:-spark 1.6

是,有一个功能需要订购

import org.apache.spark.sql.expressions.Window.orderBy
import org.apache.spark.sql.functions.{coalesce, lag}

case class Temp(a: String, b: Option[String], c: Option[String])

val input = ss.createDataFrame(
  Seq(
    Temp("A", Some("a1"), Some("a2")),
    Temp("D", Some("d1"), Some("d2")),
    Temp("B", Some("b1"), Some("b2")),
    Temp("E", None, None),
    Temp("C", None, None)
  ))

+---+----+----+
|  a|   b|   c|
+---+----+----+
|  A|  a1|  a2|
|  D|  d1|  d2|
|  B|  b1|  b2|
|  E|null|null|
|  C|null|null|
+---+----+----+

val order = orderBy($"a")
input
  .withColumn("b", coalesce($"b", lag($"b", 1).over(order)))
  .withColumn("c", coalesce($"c", lag($"c", 1).over(order)))
  .show()

+---+---+---+
|  a|  b|  c|
+---+---+---+
|  A| a1| a2|
|  B| b1| b2|
|  C| b1| b2|
|  D| d1| d2|
|  E| d1| d2|
+---+---+---+

下面是一种使用窗口函数的方法,步骤如下:

  • 添加行标识列(如果已经有一个列,则不需要),并将非键列(可能有很多)组合到一个列中
  • 使用
    last/rowsBetween
    Window函数创建带有条件空值的
    tmp1
    ,并使用
    last/rowsBetween
    窗口函数对最后一个非空值进行回填
  • 根据
    cols
    tmp2
  • 使用
    foldLeft
  • 请注意,此解决方案使用的窗口函数没有分区,因此可能不适用于大型数据集

    val df = Seq(
      ("X", "x1", "x2"),
      ("Z", "z1", "z2"),
      ("Y", "", ""),
      ("X", "x3", "x4"),
      ("P", "p1", "p2"),
      ("Q", "q1", "q2"),
      ("Y", "", "")
    ).toDF("col1", "col2", "col3")
    
    import org.apache.spark.sql.functions._
    import org.apache.spark.sql.expressions.Window
    
    val colList = df.columns.filter(_ != "col1")
    
    val df2 = df.select($"col1", monotonically_increasing_id.as("id"),
      struct(colList.map(col): _*).as("cols")
    )
    
    val df3 = df2.
      withColumn( "tmp1", when($"col1" === "X", $"cols") ).
      withColumn( "tmp2", last("tmp1", ignoreNulls = true).over(
        Window.orderBy("id").rowsBetween(Window.unboundedPreceding, 0)
      ) )
    
    df3.show
    // +----+---+-------+-------+-------+
    // |col1| id|   cols|   tmp1|   tmp2|
    // +----+---+-------+-------+-------+
    // |   X|  0|[x1,x2]|[x1,x2]|[x1,x2]|
    // |   Z|  1|[z1,z2]|   null|[x1,x2]|
    // |   Y|  2|    [,]|   null|[x1,x2]|
    // |   X|  3|[x3,x4]|[x3,x4]|[x3,x4]|
    // |   P|  4|[p1,p2]|   null|[x3,x4]|
    // |   Q|  5|[q1,q2]|   null|[x3,x4]|
    // |   Y|  6|    [,]|   null|[x3,x4]|
    // +----+---+-------+-------+-------+
    
    val df4 = df3.withColumn( "newcols",
      when($"col1" === "Y", $"tmp2").otherwise($"cols")
    ).select($"col1", $"newcols")
    
    df4.show
    // +----+-------+
    // |col1|newcols|
    // +----+-------+
    // |   X|[x1,x2]|
    // |   Z|[z1,z2]|
    // |   Y|[x1,x2]|
    // |   X|[x3,x4]|
    // |   P|[p1,p2]|
    // |   Q|[q1,q2]|
    // |   Y|[x3,x4]|
    // +----+-------+
    
    val dfResult = colList.foldLeft( df4 )(
      (accDF, c) => accDF.withColumn(c, df4(s"newcols.$c"))
    ).drop($"newcols")
    
    dfResult.show
    // +----+----+----+
    // |col1|col2|col3|
    // +----+----+----+
    // |   X|  x1|  x2|
    // |   Z|  z1|  z2|
    // |   Y|  x1|  x2|
    // |   X|  x3|  x4|
    // |   P|  p1|  p2|
    // |   Q|  q1|  q2|
    // |   Y|  x3|  x4|
    // +----+----+----+
    
    [更新]

    对于Spark 1.x,
    last(colName,ignoreNulls)
    在DataFrame API中不可用。解决方法是恢复使用Spark SQL,该SQL在其last()方法中支持ignore null:


    这在分布式框架中很难解决。。你怎么知道哪一个是其上一个X?即使我有这个疑问…但可能会有类似于滞后函数的东西我得到了一些异常“错误:重载方法值last with Alternations:”我正在删除from last的第二个参数(“tmp1”,ignoreNulls=true),然后我得到了这个异常“38:错误:value unbounddpreceiding不是object org.apache.spark.sql.expressions.Window的成员”我正在使用spark 1.6您可以将
    Window.unbounddpreceiding
    替换为
    Long.MinValue
    。不幸的是
    last(colName,ignoreNulls)
    在Spark 2.0之前不可用。请查看我的更新答案以获得解决方法。我仍然获得org.apache.Spark.sql.AnalysisException:最后找不到窗口函数;如果我做错了,请告诉我,我没有Spark 1.6环境,因此无法重现上述问题。要恢复使用Spark sql,请确保
    导入sqlContext.implicits.\u
    。这里有一个相关的示例供您参考。
    df2.
      withColumn( "tmp1", when($"col1" === "X", $"cols") ).
      createOrReplaceTempView("df2table")
      // might need to use registerTempTable("df2table") instead
    
    val df3 = spark.sqlContext.sql("""
      select col1, id, cols, tmp1, last(tmp1, true) over (
        order by id rows between unbounded preceding and current row
        ) as tmp2
      from df2table
    """)