Реляционизировать json вложенный массив - PullRequest
1 голос
/ 05 марта 2020

У меня есть следующий каталог, и я хочу использовать клей AWS для выравнивания?

| accountId | resourceId | items                                                           |
|-----------|------------|-----------------------------------------------------------------|
| 1         | r1         | [{name: "tool", version: "1.0"}, {name: "app", version: "1.0"}] |
| 1         | r2         | [{name: "tool", version: "2.0"}, {name: "app", version: "2.0"}] |
| 2         | r3         | [{name: "tool", version: "3.0"}, {name: "app", version: "3.0"}] |

Я хочу сгладить его следующим образом:

| accountId | resourceId | name | version |
|-----------|------------|------|---------|
| 1         | r1         | tool | 1.0     |
| 1         | r1         | app  | 1.0     |
| 1         | r2         | tool | 2.0     |
| 1         | r2         | app  | 2.0     |
| 2         | r3         | tool | 3.0     |
| 2         | r3         | app  | 3.0     |

Relationalize.apply может только сглаживать вложенные элементы, он не может привести к accountId и resourceId к результату, есть ли способ решить это?

1 Ответ

1 голос
/ 05 марта 2020

В Pyspark, если структура элемента массива была действительной JSON, например:

{"name": "tool", "version": "1.0"}

Вы могли бы использовать explode + from_json чтобы разобрать его на struct.

Но здесь вам нужно провести некоторую очистку. Одним из способов является использование функции str_to_map после того, как вы взорвете столбец items, чтобы получить столбец карты. Затем взорвите его снова и поверните, чтобы получить ключи карты в виде столбцов.

df = spark.createDataFrame([
    (1, "r1", ['{name: "tool", version: "1.0"}', '{name: "app", version: "1.0"}']),
    (1, "r2", ['{name: "tool", version: "2.0"}', '{name: "app", version: "2.0"}']),
    (2, "r3", ['{name: "tool", version: "3.0"}', '{name: "app", version: "3.0"}'])
], ["accountId", "resourceId", "items"])

# remove leading and trailing {} and convert to map
sql_expr = "str_to_map(trim(BOTH '{}' FROM items), ',', ':')"

df.withColumn("items", explode(col("items"))) \
  .select(col("*"), explode(expr(sql_expr))) \
  .groupBy("accountId", "resourceId", "items") \
  .pivot("key") \
  .agg(first(expr("trim(BOTH '\"' FROM trim(value))"))) \
  .drop("items")\
  .show()

#+---------+----------+--------+----+
#|accountId|resourceId| version|name|
#+---------+----------+--------+----+
#|        1|        r1|     1.0| app|
#|        1|        r2|     2.0| app|
#|        2|        r3|     3.0|tool|
#|        2|        r3|     3.0| app|
#|        1|        r2|     2.0|tool|
#|        1|        r1|     1.0|tool|
#+---------+----------+--------+----+

Другой простой способ, если вы знаете все ключи, - это использовать regexp_extract для извлечения значений из строки:

df.withColumn("items", explode(col("items"))) \
  .withColumn("name", regexp_extract("items", "name: \"(.+?)\"[,}]", 1)) \
  .withColumn("version", regexp_extract("items", "version: \"(.+?)\"[,}]", 1)) \
  .drop("items") \
  .show() 
...