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|
    +---+---+