Spark Dataframes UPSERT到Postgres表

时间:2016-01-06 21:33:05

标签: postgresql scala apache-spark dataframe apache-spark-sql

我正在使用Apache Spark DataFrames连接两个数据源并将结果作为另一个DataFrame获取。我想将结果写入另一个Postgres表。我看到了这个选项:

myDataFrame.write.jdbc(url, table, connectionProperties)

但是,我想要做的是根据表的主键将数据帧UPSERT到表中。怎么做?我正在使用Spark 1.6.0。

4 个答案:

答案 0 :(得分:17)

不支持。 DataFrameWriter可以附加或覆盖现有表。如果您的应用程序需要更复杂的逻辑,则您必须手动处理此问题。

一种选择是使用标准JDBC连接的操作(foreachforeachPartition)。另一个是写入临时文件并直接在数据库中处理其余部分。

答案 1 :(得分:13)

KrisP有权利。进行upsert的最佳方法不是通过准备好的声明。重要的是要注意,此方法将一次插入一个具有与您拥有的工作者数量一样多的分区的方法。如果你想批量做这件事你也可以

import java.sql._
dataframe.coalesce("NUMBER OF WORKERS").mapPartitions((d) => Iterator(d)).foreach { batch =>
  val dbc: Connection = DriverManager.getConnection("JDBCURL")
  val st: PreparedStatement = dbc.prepareStatement("YOUR PREPARED STATEMENT")

  batch.grouped("# Of Rows you want per batch").foreach { session =>
    session.foreach { x =>
      st.setDouble(1, x.getDouble(1)) 
      st.addBatch()
    }
    st.executeBatch()
  }
  dbc.close()
}

这将为每个worker执行批处理并关闭DB连接。它可以让您控制工人数量,批次数量,并允许您在这些范围内工作。

答案 2 :(得分:8)

如果您要手动完成并通过zero323提到的选项1,您应该看一下Spark source code for the insert statement here

  def insertStatement(conn: Connection, table: String, rddSchema: StructType): PreparedStatement = {
    val columns = rddSchema.fields.map(_.name).mkString(",")
    val placeholders = rddSchema.fields.map(_ => "?").mkString(",")
    val sql = s"INSERT INTO $table ($columns) VALUES ($placeholders)"
    conn.prepareStatement(sql)
  }

PreparedStatementpart of java.sql,其中包含execute()executeUpdate()等方法。当然,您仍然需要相应地修改sql

答案 3 :(得分:2)

要插入JDBC,您可以使用

dataframe.write.mode(SaveMode.Append).jdbc(jdbc_url,table_name,connection_properties)

此外,Dataframe.write为您提供了一个DataFrameWriter,并且它有一些插入数据帧的方法。

def insertInto(tableName: String): Unit

将DataFrame的内容插入指定的表。它要求DataFrame的模式与表的模式相同。

因为它会将数据插入现有表格,所以格式或选项将被忽略。

http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.sql.DataFrameWriter

没有任何东西可以从火花开箱即可更新个别记录