我正在尝试使用DataFrame
将DataFrameWriter
保存为Parquet格式的HDFS,并按三个列值进行分区,如下所示:
dataFrame.write.mode(SaveMode.Overwrite).partitionBy("eventdate", "hour", "processtime").parquet(path)
如this question中所述,partitionBy
将删除path
处的完整现有分区层次结构,并将其替换为dataFrame
中的分区。由于特定日期的新增量数据将定期出现,我想要的是仅替换dataFrame
具有数据的层次结构中的那些分区,而不更改其他分区。
为此,我需要使用完整路径单独保存每个分区,如下所示:
singlePartition.write.mode(SaveMode.Overwrite).parquet(path + "/eventdate=2017-01-01/hour=0/processtime=1234567890")
但是我无法理解将数据组织到单分区DataFrame
中的最佳方法,以便我可以使用它们的完整路径将它们写出来。一个想法是这样的:
dataFrame.repartition("eventdate", "hour", "processtime").foreachPartition ...
但是foreachPartition
对Iterator[Row]
进行操作,这对于写出Parquet格式并不理想。
我还考虑使用select...distinct eventdate, hour, processtime
获取分区列表,然后按每个分区过滤原始数据帧并将结果保存到完整的分区路径。但是,每个分区的独特查询加过滤器似乎效率不高,因为它会进行大量的过滤/写入操作。
我希望有一种更简洁的方法来保留dataFrame
没有数据的现有分区?
感谢阅读。
Spark版本:2.1
答案 0 :(得分:10)
模式选项Append
有一个问题!
df.write.partitionBy("y","m","d")
.mode(SaveMode.Append)
.parquet("/data/hive/warehouse/mydbname.db/" + tableName)
我已经测试并发现这将保留现有的分区文件。但是,这次出现的问题如下:如果您运行相同的代码两次(使用相同的数据),那么它将创建新的镶木地板文件,而不是替换相同数据的现有代码(Spark 1.6)。因此,我们仍然可以使用Append
解决此问题,而不是使用Overwrite
。我们应该在分区级别覆盖,而不是在表级覆盖。
df.write.mode(SaveMode.Overwrite)
.parquet("/data/hive/warehouse/mydbname.db/" + tableName + "/y=" + year + "/m=" + month + "/d=" + day)
有关详细信息,请参阅以下链接:
Overwrite specific partitions in spark dataframe write method
(我在suriyanto的评论之后更新了我的回复.Tnnx。)
答案 1 :(得分:4)
我知道这很老了。由于我看不到任何解决方案,我会继续发布一个。此方法假定您在要写入的目录上有一个配置单元表。
解决此问题的一种方法是从dataFrame
创建一个临时视图,该视图应添加到表中,然后使用普通的类似蜂巢的insert overwrite table ...
命令:
dataFrame.createOrReplaceTempView("temp_view")
spark.sql("insert overwrite table table_name partition ('eventdate', 'hour', 'processtime')select * from temp_view")
它保留旧分区,同时(仅)写入新分区。
答案 2 :(得分:2)
这是一个古老的话题,但是我遇到了同样的问题,找到了另一个解决方案,只需使用以下方法将分区覆盖模式设置为动态:
spark.conf.set('spark.sql.sources.partitionOverwriteMode', 'dynamic')
因此,我的spark会话配置如下:
spark = SparkSession.builder.appName('AppName').getOrCreate()
spark.conf.set('spark.sql.sources.partitionOverwriteMode', 'dynamic')