Python 使用pyspark分区时在循环中覆盖模式
我有一个表中天数的数据,我需要按日期分区的拼花地板文件。我必须在循环中按天保存数据,因为数据量很大,我不能把所有的日期像年份一样放在一个数据框中。我尝试了所有保存模式。在“忽略”模式下,它会在第一天保存。在“覆盖”模式下,它会保存最后一天的数据。在“追加”模式下,它添加数据。我需要的是,如果当天的数据可用,它应该忽略当天的数据,并保留已经存在的数据,但如果数据不可用,则在拼花地板文件中创建按日期分区的数据。请帮忙 如果您还想使用配置单元分区(当您调用方法Python 使用pyspark分区时在循环中覆盖模式,python,pyspark,databricks,Python,Pyspark,Databricks,我有一个表中天数的数据,我需要按日期分区的拼花地板文件。我必须在循环中按天保存数据,因为数据量很大,我不能把所有的日期像年份一样放在一个数据框中。我尝试了所有保存模式。在“忽略”模式下,它会在第一天保存。在“覆盖”模式下,它会保存最后一天的数据。在“追加”模式下,它添加数据。我需要的是,如果当天的数据可用,它应该忽略当天的数据,并保留已经存在的数据,但如果数据不可用,则在拼花地板文件中创建按日期分区的数据。请帮忙 如果您还想使用配置单元分区(当您调用方法partitionBy时,这正是您所要求的
partitionBy
时,这正是您所要求的),则当前没有PySpark SaveMode允许您在插入新分区的同时保留现有分区。请注意,有一个相反的选项,即覆盖某些分区中的数据,同时保留数据帧中没有数据的分区(将配置设置“spark.sql.sources.partitionOverwriteMode”
设置为“dynamic”
,并在写入数据集时使用保存模式。覆盖)
不过,您仍然可以通过首先创建一组所有现有分区来实现所需的功能。您可以使用PySpark,或者使用任何允许您在文件系统(如Azure Data Lake Storage Gen2)或键值存储(如AWS S3)中执行列表操作的库来实现这一点。一旦有了该列表,就可以使用它来过滤新数据集中仍要写入的数据。下面是一个仅使用PySpark的示例:
#Start and End is a range of dates.
start = date(2019, 1, 20)
end = date(2019, 1, 22)
for single_date in daterange(start, end):
query = "(SELECT ID, firstname,lastname,date FROM dbo.emp WHERE date = '%s' ) emp_alias" %((single_date).strftime("%Y-%m-%d %H:%M:%S"))
df = spark.read.jdbc(url=jdbcUrl, table=query, properties=connectionProperties)
df.write.format("parquet").mode("ignore").partitionBy("Date").save("/mnt/data/empData.parquet")
如您所见,只添加了2个分区。那些已经存在的已经被保留了下来
现在,获取现有的\u分区
数据帧需要读取数据。Spark实际上不会读取所有数据,只读取分区列和元数据。如前所述,您也可以使用与数据存储位置相关的任何API获取此数据。在我和你的例子中,看到你如何在DataRicks上写入/mnt
文件夹,我可以简单地使用内置Python函数os.walk
:dirnames=next(os.walk(dir))[1]
,并从中创建一个数据帧
顺便说一句,你看到这些行为的原因是:
忽略模式
在“忽略”模式下,它会在第一天保存
因为您使用的是for循环,并且输出目录最初可能不存在,所以将写入第一个日期分区。在for循环的所有后续迭代中,DataFrameWriter对象将不再写入,因为它认为那里已经有一些数据(第一个日期是一个分区)
覆盖模式
在“覆盖”模式下,它会保存最后一天的数据
实际上,它在for循环的每次迭代中都保存了一个分区,但是因为您指示DataFrameWriter进行覆盖,所以它将删除目录中以前存在的所有分区。所以看起来只有最后一个是写的
附加模式
在“追加”模式下,它添加数据
这不需要进一步解释
一个建议是:可能不需要多次读取数据库(使用for循环创建多个不同的查询和jdbc连接)。您可能会将查询更新为WHERE date介于%(开始)和%(结束)
,完全删除for循环并享受高效的写入
In [1]: from pyspark.sql.functions import lit
...: df = spark.range(3).withColumn("foo", lit("bar"))
...: dir = "/tmp/foo"
...: df.write.mode("overwrite").partitionBy("id").parquet(dir) # initial seeding
...: ! tree /tmp/foo
...:
...:
/tmp/foo
├── id=0
│ └── part-00001-5d14d286-81e1-4eb1-969e-c0d8089712ce.c000.snappy.parquet
├── id=1
│ └── part-00002-5d14d286-81e1-4eb1-969e-c0d8089712ce.c000.snappy.parquet
├── id=2
│ └── part-00003-5d14d286-81e1-4eb1-969e-c0d8089712ce.c000.snappy.parquet
└── _SUCCESS
3 directories, 4 files
In [2]: df2 = spark.range(5).withColumn("foo", lit("baz"))
...: existing_partitions = spark.read.parquet(dir).select("id").distinct()
...: df3 = df2.join(existing_partitions, "id", how="left_anti")
...: df3.write.mode("append").partitionBy("id").parquet(dir)
...: spark.read.parquet(dir).orderBy("id").show()
...:
...:
+---+---+
|foo| id|
+---+---+
|bar| 0|
|bar| 1|
|bar| 2|
|baz| 3|
|baz| 4|
+---+---+