簡體   English   中英

有沒有辦法用pyspark動態創建模式信息而不是輸出jsonfile中的轉義字符?

[英]Is there a way to create schema information dynamically with pyspark and not escape characters in output jsonfile?

目前pyspark格式為logFile,然后加載redshift。

分析關於以json格式輸出的logFile的每個項目,添加一個項目,並將其加載到Redshift中。 但是,每種類型的某些項目的格式不同。 (對於同一項目,預先應用Shcema。)即使輸出原樣,也會輸入轉義字符。有沒有辦法動態創建架構信息並輸出jsonfile沒有轉義字符?

- 環境 -

- spark 2.4.0
- python version 2.7.15

- DataFrame -

>> df.printSchema()
root
 |-- Name: string (nullable = false)
 |-- d: map (nullable = false)
 |    |-- key: string
 |    |-- value: string (valueContainsNull = true)

>> df.show(2,False)
+------+------------------------------------------------------------+
|Name  |d                                                           |
+------+------------------------------------------------------------+
|Amber |[Body -> {"City": "Oregon", "Country": "US"}, BodyType -> 1]|
|Alfred|[Body -> {"Weight": 80, "Height": 176}, BodyType -> 2]      |
+------+------------------------------------------------------------+

- 架構(適用於常見項目) -

>> print(json.dumps(schema.jsonValue(), indent=2))
{
  "fields": [
    {
      "metadata": {}, 
      "type": "string", 
      "name": "Name", 
      "nullable": false
    }, 
    {
      "metadata": {}, 
      "type": {
        "keyType": "string", 
        "type": "map", 
        "valueType": "string", 
        "valueContainsNull": true
      }, 
      "name": "d", 
      "nullable": false
    }
  ], 
  "type": "struct"
}

- 代碼 -

from pyspark.sql.types import *

rdd = sc.parallelize([("Amber", {"Body": "{\"City\": \"Oregon\", \"Country\": \"US\"}", "BodyType": 1}), ("Alfred", {"Body": "{\"Weight\": 80, \"Height\": 176}", "BodyType": 2})])
schema = StructType([StructField('Name',StringType(), False)
    ,StructField('d',MapType(StringType(),StringType()), False)])
df = spark.createDataFrame(rdd, schema)

- 輸出json文件 -

{"Name":"Amber","d":{"Body":"{\"City\": \"Oregon\", \"Country\": \"US\"}","BodyType":"1"}}
{"Name":"Alfred","d":{"Body":"{\"Weight\": 80, \"Height\": 176}","BodyType":"2"}}

- 輸出json文件(理想) -

{"Name":"Amber","d":{"Body":"{\"City\": \"Oregon\", \"Country\": \"US\"}","BodyType":"1"}, "Body":{"City": "Oregon", "Country": "US"}}
{"Name":"Alfred","d":{"Body":"{\"Weight\": 80, \"Height\": 176}","BodyType":"2"}, "Body":{"Weight": 80, "Height": 176}}

我試圖使用pyspark.sql.functions的schema_of_json()和from_json(),但它沒有用。 (schema_of_json只能接受字符文字)

- 試驗結果 -

from pyspark.sql.functions import schema_of_json
from pyspark.sql.functions import from_json
df = df.withColumn('Body', df.select(from_json(df.d.body,schema_of_json(df.d.Body))))

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/spark-2.4.0-bin-hadoop2.7/python/pyspark/sql/functions.py", line 2277, in from_json
    jc = sc._jvm.functions.from_json(_to_java_column(col), schema, options)
  File "/usr/local/spark-2.4.0-bin-hadoop2.7/python/lib/py4j-0.10.7-src.zip/py4j/java_gateway.py", line 1257, in __call__
  File "/usr/local/spark-2.4.0-bin-hadoop2.7/python/pyspark/sql/utils.py", line 69, in deco
    raise AnalysisException(s.split(': ', 1)[1], stackTrace)
pyspark.sql.utils.AnalysisException: u"Schema should be specified in DDL format as a string literal or output of the schema_of_json function instead of schemaofjson(`d`['Body']);"

TL;博士

簡短的回答是否定的,沒有辦法動態地推斷每一行上的模式,最終得到一個不同行具有不同模式的列。

但是,有一種方法可以輸出所需的json字符串,並將不同的json調和為一個通用的,豐富類型的模式

細節

如果它被允許它將是極其緩慢,但更重要的是它是不允許的,因為它打破了允許SparkSQL一致運行的關系模型。

數據框由列(字段)組成,列只有一種數據類型; 數據類型表示整個列。 考慮到Python的性質,它在Pyspark中並不是嚴格可執行的,但它在運行時很重要,因此該語句仍然適用。

在您的示例中,如果您想使用d.Body.City東西投射 City屬性,那么Alfred和Amber都必須存在。 至少,即使沒有值,該字段的元數據也必須存在。 執行引擎需要快速知道路徑是否無效,以避免每行的無意義掃描。

在一個列中協調多個類型的一些方法是(我確信我還有更多想法):

  1. 使用變量/ union /選項類型(例如將所有常見和不常見的json模式聯合在一起)
  2. 將它序列化為某種東西,比如json字符串(這是你在應用jsonschema之前開始的地方,非常適合傳輸數據,不適合分析)
  3. 將其向上轉換為具有最低公共分母行為/接口/屬性的超類型,盒裝或通用對象(如RDD)(丟失元數據和子類型的屬性)
  4. 不要將它存儲為單個類型,例如將單獨的變體存儲到不同的列中,並在每個列上使用不同的json模式

在這種情況下,我喜歡(1),但(4)可以作為查找通用模式的臨時步驟有效。

您的示例“常見”json架構更像選項(3)。 你在地圖里面稱為“d”(我猜因為它是一個字典?)沒有掃描數據就無法獲得有關字段的信息。

root
 |-- Name: string (nullable = false)
 |-- d: map (nullable = false)
 |    |-- key: string
 |    |-- value: string (valueContainsNull = true)

我意識到這只是添加包含Body的新列的臨時步驟,但要做到這一點,您必須將該映射中的所有可能鍵枚舉為更有用的模式。

通用(通用)模式不是string -> string的通用映射,我認為它在下面更有用。 它接近您最初嘗試但不是動態的,並且對兩行都有效。 請注意,對於所有屬性, nullable是默認的True

schema_body = StructType([
    StructField("City", StringType()),
    StructField("Country", StringType()),
    StructField("Weight", IntegerType()),
    StructField("Height", IntegerType())
])

df = df.withColumn("Body", from_json("d.Body", schema_body))
df.printSchema()

root
 |-- Name: string (nullable = false)
 |-- d: map (nullable = false)
 |    |-- key: string
 |    |-- value: string (valueContainsNull = true)
 |-- Body: struct (nullable = true)
 |    |-- City: string (nullable = true)
 |    |-- Country: string (nullable = true)
 |    |-- Weight: integer (nullable = true)
 |    |-- Height: integer (nullable = true)


df.show(2, False)

+------+---------------------------------------------------------------+---------------------+
|Name  |d                                                              |Body                 |
+------+---------------------------------------------------------------+---------------------+
|Amber |Map(Body -> {"City": "Oregon", "Country": "US"}, BodyType -> 1)|[Oregon,US,null,null]|
|Alfred|Map(Body -> {"Weight": 80, "Height": 176}, BodyType -> 2)      |[null,null,80,176]   |
+------+---------------------------------------------------------------+---------------------+

現在,你可以得到的Body.City通過選擇容易d.Body.City ,而無需擔心這行有市。

對於下一步,您可以將其恢復為json字符串

df = df.withColumn("Body", to_json("d.Body"))

您也可以將它與上一步結合使用

df = df.withColumn("Body", to_json(from_json("d.Body", schema_body)))
df.printSchema()
root
 |-- Name: string (nullable = false)
 |-- BodyAttributes: struct (nullable = true)
 |    |-- Body: string (nullable = true)
 |    |-- BodyType: integer (nullable = true)
 |-- Body: string (nullable = true)

df.show(2, False)

+------+---------------------------------------+--------------------------------+
|Name  |BodyAttributes                         |Body                            |
+------+---------------------------------------+--------------------------------+
|Amber |[{"City": "Oregon", "Country": "US"},1]|{"City":"Oregon","Country":"US"}|
|Alfred|[{"Weight": 80, "Height": 176},2]      |{"Weight":80,"Height":176}      |
+------+---------------------------------------+--------------------------------+

請注意,將其轉換回json字符串時,這些NULL值將消失。 它現在也是一個jsonstring,很容易寫入文件,你想要的。


走得更遠

如果您將此作為流程的一部分,使數據可用於分析,報告或其他目的,我會做這樣的事情

schema = StructType([
    StructField('Name',StringType(), False),
    StructField(
        'd',
        StructType([
            StructField("Body", StringType()),
            StructField("BodyType", IntegerType())
        ])
    )
])

df = spark.createDataFrame(rdd, schema)
df = df.withColumn(
    "Body", 
    from_json("d.Body", schema_body)
).withColumn(
    "BodyType", 
    col("d.BodyType")
).drop("d")

df.printSchema()

root
 |-- Name: string (nullable = false)
 |-- Body: struct (nullable = true)
 |    |-- City: string (nullable = true)
 |    |-- Country: string (nullable = true)
 |    |-- Weight: integer (nullable = true)
 |    |-- Height: integer (nullable = true)
 |-- BodyType: integer (nullable = true)


df.show(2, False)

+------+---------------------+--------+
|Name  |Body                 |BodyType|
+------+---------------------+--------+
|Amber |[Oregon,US,null,null]|1       |
|Alfred|[null,null,80,176]   |2       |
+------+---------------------+--------+

然后你可以選擇Body.CityBody.CountryBody.Weight, Body.Height`

你可以再邁一步,但這實際上取決於它們中有多少可能的Body鍵以及它有多么稀疏。

df = df.withColumn(
    "City", col("Body.City")
).withColumn(
    "Country", col("Body.Country")
).withColumn(
    "Weight", col("Body.Weight")
).withColumn(
    "Height", col("Body.Height")
).drop("Body")

df.printSchema()

root
 |-- Name: string (nullable = false)
 |-- BodyType: integer (nullable = true)
 |-- City: string (nullable = true)
 |-- Country: string (nullable = true)
 |-- Weight: integer (nullable = true)
 |-- Height: integer (nullable = true)

df.show(2, False)

+------+--------+------+-------+------+------+
|Name  |BodyType|City  |Country|Weight|Height|
+------+--------+------+-------+------+------+
|Amber |1       |Oregon|US     |null  |null  |
|Alfred|2       |null  |null   |80    |176   |
+------+--------+------+-------+------+------+

暫無
暫無

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

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