簡體   English   中英

使用pyspark分區時循環覆蓋模式

[英]Overwrite mode in loop when partition using 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")

我有一個表中的天數數據,我需要按日期分區的鑲木地板文件。 我必須按天循環保存,因為數據很大,而且我不能將所有日子都像年份數據一樣放在一個數據框中。 我嘗試了所有保存模式。 在“忽略”模式下,它會保存第一天。 在“覆蓋”模式下,它保存最后一天。 在“追加”模式下,它添加數據。 我需要的是,如果當天的數據可用,則應忽略當天的數據並保留已有的數據,但如果數據不可用,則在按日期分區的鑲木地板文件中創建。 請幫忙。

當前沒有 PySpark SaveMode 允許您在插入新分區的同時保留現有分區,如果您還想使用 Hive 分區(這是您在調用partitionBy方法時所要求的)。 請注意,有一個選項可以做相反的事情,即覆蓋某些分區中的數據,同時保留 DataFrame 中沒有數據的那些(將配置設置"spark.sql.sources.partitionOverwriteMode"設置為"dynamic"並在寫入數據集時使用SaveMode.Overwrite )。

您仍然可以通過首先創建一組所有現有分區來實現您想要的。 您可以使用 PySpark 或使用任何允許您在文件系統(如 Azure Data Lake Storage Gen2)或鍵值存儲(如 AWS S3)中執行列表操作的庫來做到這一點。 獲得該列表后,您可以使用它來過濾新數據集以查找仍要寫入的數據。 這是一個只有 PySpark 的例子:

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

如您所見,只添加了 2 個分區。 已經存在的那些被保留了下來。

現在,獲取existing_partitions DataFrame 需要讀取數據。 不過,Spark 實際上不會讀取所有數據,只會讀取分區列和元數據。 如前所述,您也可以使用與數據存儲位置相關的任何 API 來獲取這些數據。 在我和你的特殊情況下,看到你如何寫入 Databricks 上的/mnt文件夾,我可以簡單地使用內置的 Python 函數os.walkdirnames = next(os.walk(dir))[1] ,並從中創建了一個 DataFrame 。

順便說一下,你得到你所看到的行為的原因是:

  1. 忽略模式

    在“忽略”模式下,它會保存第一天。

    因為您使用的是 for 循環並且輸出目錄最初可能不存在,所以將寫入第一個日期分區。 在 for 循環的所有后續迭代中,DataFrameWriter 對象將不再寫入,因為它認為那里已經有一些數據(一個分區,第一個日期)。

  2. 覆蓋模式

    在“覆蓋”模式下,它保存最后一天。

    實際上,它在 for 循環的每次迭代中都保存了一個分區,但是因為您指示 DataFrameWriter 進行覆蓋,它將刪除目錄中所有先前存在的分區。 所以看起來只寫了最后一個。

  3. 追加模式

    在'append'模式下,它添加數據。這個不需要進一步解釋。

一個建議:可能不需要多次從數據庫讀取(使用 for 循環創建多個不同的查詢和 jdbc 連接)。 您可能會更新查詢以包含WHERE date BETWEEN %(start) AND %(end) ,完全刪除 for 循環並享受高效寫入。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM