[英]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']);"
簡短的回答是否定的,沒有辦法動態地推斷每一行上的模式,最終得到一個不同行具有不同模式的列。
但是,有一種方法可以輸出所需的json字符串,並將不同的json調和為一個通用的,豐富類型的模式
如果它被允許它將是極其緩慢,但更重要的是它是不允許的,因為它打破了允許SparkSQL一致運行的關系模型。
數據框由列(字段)組成,列只有一種數據類型; 數據類型表示整個列。 考慮到Python的性質,它在Pyspark中並不是嚴格可執行的,但它在運行時很重要,因此該語句仍然適用。
在您的示例中,如果您想使用d.Body.City
東西投射 City
屬性,那么Alfred和Amber都必須存在。 至少,即使沒有值,該字段的元數據也必須存在。 執行引擎需要快速知道路徑是否無效,以避免每行的無意義掃描。
在一個列中協調多個類型的一些方法是(我確信我還有更多想法):
在這種情況下,我喜歡(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.City
, Body.Country
, Body.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.