繁体   English   中英

在 Scala Spark 中,如何拆分一列,使前半部分成为列名,第二部分成为列值?

[英]How do you split a column such that first half becomes the column name and the second the column value in Scala Spark?

我有一个具有类似价值的列

+----------------------+-----------------------------------------+
|UserId                |col                                      |
+----------------------+-----------------------------------------+
|1                     |firstname=abc                            |
|2                     |lastname=xyz                             |
|3                     |firstname=pqr;lastname=zzz               |
|4                     |firstname=aaa;middlename=xxx;lastname=bbb|
+----------------------+-----------------------------------------+

我想要的是这样的:

+----------------------+--------------------------------+
|UserId                |firstname | lastname| middlename|
+----------------------+--------------------------------+
|1                     |abc       | null    | null      |
|2                     |null      | xyz     | null      |
|3                     |pqr       | zzz     | null      |
|4                     |aaa       | bbb     | xxx       |
+----------------------+--------------------------------+

我已经这样做了:

var new_df = df.withColumn("temp_new", split(col("col"), "\\;")).select(
     (0 until numCols).map(i => split(col("temp_new").getItem(i), "=").getItem(1).as(s"col$i")): _*
)

其中numCols是 col 的最大长度

但正如您可能已经猜到的那样,我得到了这样的输出:

+----------------------+--------------------------------+
|UserId                |col0      | col1    | col2      |
+----------------------+--------------------------------+
|1                     |abc       | null    | null      |
|2                     |xyz       | null    | null      |
|3                     |pqr       | zzz     | null      |
|4                     |aaa       | xxx     | bbb       |
+----------------------+--------------------------------+

注意:以上只是一个例子。 对于大约 40-50 个列名和值,可能会有更多添加到列中,例如firstname=aaa;middlename=xxx;lastname=bbb;age=20;country=India等等。 它们是动态的,我事先不知道其中的大部分

我正在寻找一种在 Spark 中使用 Scala 实现结果的方法。

通过 SQL 函数str_to_mapkey/value-pairs字符串列转换为Map列后,您可以应用groupBy/pivot生成key列,如下所示:

val df = Seq(
  (1, "firstname=joe;age=33"),
  (2, "lastname=smith;country=usa"),
  (3, "firstname=zoe;lastname=cooper;age=44;country=aus"),
  (4, "firstname=john;lastname=doe")
).toDF("user_id", "key_values")

df.
  select($"user_id", explode(expr("str_to_map(key_values, ';', '=')"))).
  groupBy("user_id").pivot("key").agg(first("value").as("value")).
  orderBy("user_id").  // only for ordered output
  show
/*
+-------+----+-------+---------+--------+
|user_id| age|country|firstname|lastname|
+-------+----+-------+---------+--------+
|      1|  33|   null|      joe|    null|
|      2|null|    usa|     null|   smith|
|      3|  44|    aus|      zoe|  cooper|
|      4|null|   null|     john|     doe|
+-------+----+-------+---------+--------+
*/

由于您的数据由; 然后你的键值对被分割=你可以考虑使用str_to_map如下:

  1. 创建数据的临时视图,例如
df.createOrReplaceTempView("my_table")
  1. 在您的 spark 会话中运行以下命令
result_df = sparkSession.sql("<insert sql below here>")
WITH split_data AS (
    SELECT
        UserId,
        str_to_map(col,';','=') full_name
    FROM
        my_table
)
SELECT
    UserId,
    full_name['firstname'] as firstname,
    full_name['lastname'] as lastname,
    full_name['middlename'] as middlename
FROM
    split_data

此解决方案是根据其他答案的评论部分中描述的扩展要求提出的:

  1. key_values存在重复键
  2. 只有重复的键列才会聚合为ArrayType

可能还有其他方法。 下面的解决方案使用groupBy/pivotcollect_list ,然后从非重复键列中提取单个元素(如果为空,则为 null)。

val df = Seq(
  (1, "firstname=joe;age=33;moviegenre=comedy"),
  (2, "lastname=smith;country=usa;moviegenre=drama"),
  (3, "firstname=zoe;lastname=cooper;age=44;country=aus"),
  (4, "firstname=john;lastname=doe;moviegenre=drama;moviegenre=comedy")
).toDF("user_id", "key_values")

val mainCols = df.columns diff Seq("key_values")

val dfNew = df.
  withColumn("kv_arr", split($"key_values", ";")).
  withColumn("kv", explode(expr("transform(kv_arr, kv -> split(kv, '='))"))).
  groupBy("user_id").pivot($"kv"(0)).agg(collect_list($"kv"(1)))

val dupeKeys = Seq("moviegenre")  // user-provided
val nonDupeKeys = dfNew.columns diff (mainCols ++ dupeKeys)

dfNew.select(
    mainCols.map(col) ++
    dupeKeys.map(col) ++
    nonDupeKeys.map(k => when(size(col(k)) > 0, col(k)(0)).as(k)): _*
  ).
  orderBy("user_id").  // only for ordered output
  show
/*
+-------+---------------+----+-------+---------+--------+
|user_id|     moviegenre| age|country|firstname|lastname|
+-------+---------------+----+-------+---------+--------+
|      1|       [comedy]|  33|   null|      joe|    null|
|      2|        [drama]|null|    usa|     null|   smith|
|      3|             []|  44|    aus|      zoe|  cooper|
|      4|[drama, comedy]|null|   null|     john|     doe|
+-------+---------------+----+-------+---------+--------+
/*

请注意,高阶函数transform用于处理键/值拆分,因为 SQL 函数str_to_map (在原始解决方案中使用)无法处理重复键。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM