Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/apache-spark/6.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Apache spark 覆盖spark数据帧写入方法中的特定分区_Apache Spark_Apache Spark Sql_Spark Dataframe - Fatal编程技术网

Apache spark 覆盖spark数据帧写入方法中的特定分区

Apache spark 覆盖spark数据帧写入方法中的特定分区,apache-spark,apache-spark-sql,spark-dataframe,Apache Spark,Apache Spark Sql,Spark Dataframe,我想覆盖特定的分区,而不是spark中的所有分区。我正在尝试以下命令: df.write.orc('maprfs:///hdfs-base-path','overwrite',partitionBy='col4') 其中df是具有要覆盖的增量数据的数据帧 hdfs基本路径包含主数据 当我尝试上面的命令时,它会删除所有分区,并将df中存在的分区插入hdfs路径 我的要求是只覆盖df中指定hdfs路径上的那些分区。有人能帮我一下吗?这是一个常见的问题。Spark高达2.0的唯一解决方案是直接写入分

我想覆盖特定的分区,而不是spark中的所有分区。我正在尝试以下命令:

df.write.orc('maprfs:///hdfs-base-path','overwrite',partitionBy='col4')
其中df是具有要覆盖的增量数据的数据帧

hdfs基本路径包含主数据

当我尝试上面的命令时,它会删除所有分区,并将df中存在的分区插入hdfs路径


我的要求是只覆盖df中指定hdfs路径上的那些分区。有人能帮我一下吗?

这是一个常见的问题。Spark高达2.0的唯一解决方案是直接写入分区目录,例如

df.write.mode(SaveMode.Overwrite).save("/root/path/to/data/partition_col=value")
如果您使用的是Spark 2.0之前的版本,则需要使用以下方法阻止Spark发出元数据文件(因为它们会破坏自动分区发现):

如果您使用的是Spark 1.6.2之前的版本,您还需要删除
/root/path/to/data/partition\u col=value
中的
\u SUCCESS
文件,否则它的存在将中断自动分区发现。(我强烈建议使用1.6.2或更高版本。)

您可以从我的Spark Summit谈话中获得有关如何管理大型分区表的更多详细信息。

使用Spark 1.6

HiveContext可以大大简化此过程。关键是您必须首先使用定义了分区的
createexternaltable
语句在配置单元中创建表。例如:

# Hive SQL
CREATE EXTERNAL TABLE test
(name STRING)
PARTITIONED BY
(age INT)
STORED AS PARQUET
LOCATION 'hdfs:///tmp/tables/test'
从这里开始,假设您有一个数据框,其中包含特定分区(或多个分区)的新记录。您可以使用HiveContext SQL语句使用此数据框执行
INSERT OVERWRITE
,这将仅覆盖数据框中包含的分区的表:

# PySpark
hiveContext = HiveContext(sc)
update_dataframe.registerTempTable('update_dataframe')

hiveContext.sql("""INSERT OVERWRITE TABLE test PARTITION (age)
                   SELECT name, age
                   FROM update_dataframe""")
注意:
update\u dataframe
在本例中具有与目标
test
表匹配的架构

这种方法容易犯的一个错误是跳过配置单元中的
createexternaltable
步骤,只使用dataframeapi的write方法生成表。特别是对于基于拼花地板的表,该表的定义不适合支持配置单元的
插入覆盖。。。分区
函数


希望这有帮助

如果使用DataFrame,可能需要在数据上使用配置单元表。 在这种情况下,您只需要调用方法

df.write.mode(SaveMode.Overwrite).partitionBy("partition_col").insertInto(table_name)
它将覆盖DataFrame包含的分区

没有必要指定格式(orc),因为Spark将使用配置单元表格式


它在Spark版本1.6中运行良好

您可以执行类似操作以使作业可重入(幂等): (在spark 2.2上试用过)

#删除分区
drop_query=“ALTER TABLE_name drop IF EXISTS PARTITION(PARTITION_col='{val}')”。格式(val=target_PARTITION)
打印下拉查询
sql(drop\u查询)
#删除目录
dbutils.fs.rm(,recurse=True)
#加载分区
写\
.partitionBy(“partition_col”)\
.saveAsTable(表名,format=“parquet”,mode=“append”,path=)

终于!这现在是Spark 2.3.0中的一项功能:

要使用它,您需要将spark.sql.sources.partitionOverwriteMode设置为dynamic,数据集需要分区,写入模式为overwrite。例如:

spark.conf.set("spark.sql.sources.partitionOverwriteMode","dynamic")
data.write.mode("overwrite").insertInto("partitioned_table")
我建议在编写之前根据分区列进行重新分区,这样每个文件夹就不会有400个文件


在Spark 2.3.0之前,最好的解决方案是启动SQL语句删除这些分区,然后使用模式append写入它们。

我建议您进行清理,然后使用模式append写入新分区:

import scala.sys.process._
def deletePath(path: String): Unit = {
    s"hdfs dfs -rm -r -skipTrash $path".!
}

df.select(partitionColumn).distinct.collect().foreach(p => {
    val partition = p.getAs[String](partitionColumn)
    deletePath(s"$path/$partitionColumn=$partition")
})

df.write.partitionBy(partitionColumn).mode(SaveMode.Append).orc(path)
这将只删除新分区。写入数据后,如果需要更新metastore,请运行此命令:

sparkSession.sql(s"MSCK REPAIR TABLE $db.$table")

注意:
deletePath
假设系统上有可用的
hfds
命令。

我尝试了下面的方法来覆盖配置单元表中的特定分区

### load Data and check records
    raw_df = spark.table("test.original")
    raw_df.count()

lets say this table is partitioned based on column : **c_birth_year** and we would like to update the partition for year less than 1925


### Check data in few partitions.
    sample = raw_df.filter(col("c_birth_year") <= 1925).select("c_customer_sk", "c_preferred_cust_flag")
    print "Number of records: ", sample.count()
    sample.show()


### Back-up the partitions before deletion
    raw_df.filter(col("c_birth_year") <= 1925).write.saveAsTable("test.original_bkp", mode = "overwrite")


### UDF : To delete particular partition.
    def delete_part(table, part):
        qry = "ALTER TABLE " + table + " DROP IF EXISTS PARTITION (c_birth_year = " + str(part) + ")"
        spark.sql(qry)


### Delete partitions
    part_df = raw_df.filter(col("c_birth_year") <= 1925).select("c_birth_year").distinct()
    part_list = part_df.rdd.map(lambda x : x[0]).collect()

    table = "test.original"
    for p in part_list:
        delete_part(table, p)


### Do the required Changes to the columns in partitions
    df = spark.table("test.original_bkp")
    newdf = df.withColumn("c_preferred_cust_flag", lit("Y"))
    newdf.select("c_customer_sk", "c_preferred_cust_flag").show()


### Write the Partitions back to Original table
    newdf.write.insertInto("test.original")


### Verify data in Original table
    orginial.filter(col("c_birth_year") <= 1925).select("c_customer_sk", "c_preferred_cust_flag").show()



Hope it helps.

Regards,

Neeraj
###加载数据并检查记录
原始df=火花表(“原始测试”)
原始密度计数()
假设此表是根据列:*c_birth_year**进行分区的,我们希望在1925年之前更新分区
###检查几个分区中的数据。

sample=raw_df.filter(col(“c_birth_year”)我建议您创建一个类似于目标表的临时表,并在其中插入数据,而不是直接写入目标表

CREATE TABLE tmpTbl LIKE trgtTbl LOCATION '<tmpLocation';
然后,通过执行以下操作恢复表分区路径:

MSCK REPAIR TABLE tmpTbl;
通过查询配置单元元数据获取分区路径,如:

SHOW PARTITONS tmpTbl;

trgtTbl
中删除这些分区,并将目录从
tmpTbl
移动到
trgtTbl

,正如jatin所写,您可以从配置单元和路径中删除分区,然后追加数据 由于我在这方面浪费了太多时间,我为其他spark用户添加了以下示例。 我在spark 2.2.1中使用了Scala

  import org.apache.hadoop.conf.Configuration
  import org.apache.hadoop.fs.Path
  import org.apache.spark.SparkConf
  import org.apache.spark.sql.{Column, DataFrame, SaveMode, SparkSession}

  case class DataExample(partition1: Int, partition2: String, someTest: String, id: Int)

 object StackOverflowExample extends App {
//Prepare spark & Data
val sparkConf = new SparkConf()
sparkConf.setMaster(s"local[2]")
val spark = SparkSession.builder().config(sparkConf).getOrCreate()
val tableName = "my_table"

val partitions1 = List(1, 2)
val partitions2 = List("e1", "e2")
val partitionColumns = List("partition1", "partition2")
val myTablePath = "/tmp/some_example"

val someText = List("text1", "text2")
val ids = (0 until 5).toList

val listData = partitions1.flatMap(p1 => {
  partitions2.flatMap(p2 => {
    someText.flatMap(
      text => {
        ids.map(
          id => DataExample(p1, p2, text, id)
        )
      }
    )
  }
  )
})

val asDataFrame = spark.createDataFrame(listData)

//Delete path function
def deletePath(path: String, recursive: Boolean): Unit = {
  val p = new Path(path)
  val fs = p.getFileSystem(new Configuration())
  fs.delete(p, recursive)
}

def tableOverwrite(df: DataFrame, partitions: List[String], path: String): Unit = {
  if (spark.catalog.tableExists(tableName)) {
    //clean partitions
    val asColumns = partitions.map(c => new Column(c))
    val relevantPartitions = df.select(asColumns: _*).distinct().collect()
    val partitionToRemove = relevantPartitions.map(row => {
      val fields = row.schema.fields
      s"ALTER TABLE ${tableName} DROP IF EXISTS PARTITION " +
        s"${fields.map(field => s"${field.name}='${row.getAs(field.name)}'").mkString("(", ",", ")")} PURGE"
    })

    val cleanFolders = relevantPartitions.map(partition => {
      val fields = partition.schema.fields
      path + fields.map(f => s"${f.name}=${partition.getAs(f.name)}").mkString("/")
    })

    println(s"Going to clean ${partitionToRemove.size} partitions")
    partitionToRemove.foreach(partition => spark.sqlContext.sql(partition))
    cleanFolders.foreach(partition => deletePath(partition, true))
  }
  asDataFrame.write
    .options(Map("path" -> myTablePath))
    .mode(SaveMode.Append)
    .partitionBy(partitionColumns: _*)
    .saveAsTable(tableName)
}

//Now test
tableOverwrite(asDataFrame, partitionColumns, tableName)
spark.sqlContext.sql(s"select * from $tableName").show(1000)
tableOverwrite(asDataFrame, partitionColumns, tableName)

import spark.implicits._

val asLocalSet = spark.sqlContext.sql(s"select * from $tableName").as[DataExample].collect().toSet
if (asLocalSet == listData.toSet) {
  println("Overwrite is working !!!")
}

}使用Scala在Spark 2.3.1上对此进行了测试。 上面的大多数答案都是写入配置单元表。但是,我想直接写入磁盘,该磁盘在该文件夹顶部有一个
外部配置单元表

首先是所需的配置

val sparkSession: SparkSession = SparkSession
      .builder
      .enableHiveSupport()
      .config("spark.sql.sources.partitionOverwriteMode", "dynamic") // Required for overwriting ONLY the required partitioned folders, and not the entire root folder
      .appName("spark_write_to_dynamic_partition_folders")
此处的用法:

DataFrame
.write
.format("<required file format>")
.partitionBy("<partitioned column name>")
.mode(SaveMode.Overwrite) // This is required.
.save(s"<path_to_root_folder>")
数据帧
写
.格式(“”)
.partitionBy(“”)
.mode(SaveMode.Overwrite)//这是必需的。
.save(s“”)

在insertInto语句中添加“overwrite=True”参数可以解决以下问题:

hiveContext.setConf("hive.exec.dynamic.partition", "true")
hiveContext.setConf("hive.exec.dynamic.partition.mode", "nonstrict")

df.write.mode("overwrite").insertInto("database_name.partioned_table", overwrite=True)
默认情况下
overwrite=False
。将其更改为
True
允许我们覆盖
df
和分区_表中包含的特定分区。这有助于我们避免使用
df
覆盖分区_表的全部内容,因为>=Spark 2.3.0:

spark.conf.set("spark.sql.sources.partitionOverwriteMode","dynamic")
data.write.insertInto("partitioned_table", overwrite=True)

这对我在AWS Glue ETL作业(Glue 1.0-Spark 2.4-Python 2)上的工作很有效。

非常感谢Sim的回答。还有一些疑问,如果假设初始数据帧有大约100个分区的数据,那么我应该
DataFrame
.write
.format("<required file format>")
.partitionBy("<partitioned column name>")
.mode(SaveMode.Overwrite) // This is required.
.save(s"<path_to_root_folder>")
hiveContext.setConf("hive.exec.dynamic.partition", "true")
hiveContext.setConf("hive.exec.dynamic.partition.mode", "nonstrict")

df.write.mode("overwrite").insertInto("database_name.partioned_table", overwrite=True)
spark.conf.set("spark.sql.sources.partitionOverwriteMode","dynamic")
data.write.insertInto("partitioned_table", overwrite=True)
spark.conf.set("spark.sql.sources.partitionOverwriteMode","dynamic")
data.toDF().write.mode("overwrite").format("parquet").partitionBy("date", "name").save("s3://path/to/somewhere")