我需要合并两个数据帧并按键组合列。这两个数据库具有相同的模式,例如:
root
|-- id: String (nullable = true)
|-- cMap: map (nullable = true)
| |-- key: string
| |-- value: string (valueContainsNull = true)
我想按" id"并聚合" cMap"一起进行重复数据删除。 我尝试了代码:
val df = df_a.unionAll(df_b).groupBy("id").agg(collect_list("cMap") as "cMap").
rdd.map(x => {
var map = Map[String,String]()
x.getAs[Seq[Map[String,String]]]("cMap").foreach( y =>
y.foreach( tuple =>
{
val key = tuple._1
val value = tuple._2
if(!map.contains(key))//deduplicate
map += (key -> value)
}))
Row(x.getAs[String]("id"),map)
})
但似乎collect_list不能用于映射结构:
org.apache.spark.sql.AnalysisException: No handler for Hive udf class org.apache.hadoop.hive.ql.udf.generic.GenericUDAFCollectList because: Only primitive type arguments are accepted but map<string,string> was passed as parameter 1..;
该问题还有其他解决方案吗?
答案 0 :(得分:3)
您必须首先在地图列上使用explode
函数将 destructure 映射到键和值列,union
结果数据集后跟distinct
到取消重复,然后groupBy
使用一些自定义Scala编码来聚合地图。
停止说话然后让我们做一些编码...
给出数据集:
scala> a.show(false)
+---+-----------------------+
|id |cMap |
+---+-----------------------+
|one|Map(1 -> one, 2 -> two)|
+---+-----------------------+
scala> a.printSchema
root
|-- id: string (nullable = true)
|-- cMap: map (nullable = true)
| |-- key: string
| |-- value: string (valueContainsNull = true)
scala> b.show(false)
+---+-------------+
|id |cMap |
+---+-------------+
|one|Map(1 -> one)|
+---+-------------+
scala> b.printSchema
root
|-- id: string (nullable = true)
|-- cMap: map (nullable = true)
| |-- key: string
| |-- value: string (valueContainsNull = true)
您应首先在地图列上使用explode功能。
explode(e:Column):Column 为给定数组或地图列中的每个元素创建一个新行。
val a_keyValues = a.select('*, explode($"cMap"))
scala> a_keyValues.show(false)
+---+-----------------------+---+-----+
|id |cMap |key|value|
+---+-----------------------+---+-----+
|one|Map(1 -> one, 2 -> two)|1 |one |
|one|Map(1 -> one, 2 -> two)|2 |two |
+---+-----------------------+---+-----+
val b_keyValues = b.select('*, explode($"cMap"))
使用以下内容,您可以使用不同的键值对,这正是您要求的重复数据删除。
val distinctKeyValues = a_keyValues.
union(b_keyValues).
select("id", "key", "value").
distinct // <-- deduplicate
scala> distinctKeyValues.show(false)
+---+---+-----+
|id |key|value|
+---+---+-----+
|one|1 |one |
|one|2 |two |
+---+---+-----+
groupBy
的时间并创建最终的地图列。
val result = distinctKeyValues.
withColumn("map", map($"key", $"value")).
groupBy("id").
agg(collect_list("map")).
as[(String, Seq[Map[String, String]])]. // <-- leave Rows for typed pairs
map { case (id, list) => (id, list.reduce(_ ++ _)) }. // <-- collect all entries under one map
toDF("id", "cMap") // <-- give the columns their names
scala> result.show(truncate = false)
+---+-----------------------+
|id |cMap |
+---+-----------------------+
|one|Map(1 -> one, 2 -> two)|
+---+-----------------------+
请注意,从Spark 2.0.0开始unionAll已被弃用且union
是正确的联合运营商:
(从版本2.0.0开始)使用union()
答案 1 :(得分:0)
collect_list
适用于您的情况。您使用的是什么版本的火花?
使用2.X这样可以正常使用
val data = Seq(
(1, Map(1 -> "a")),
(1, Map(2 -> "b")),
(1, Map(3 -> "c")),
(2, Map(1 -> "a")),
(2, Map(2 -> "b")),
(2, Map(3 -> "c"))
).toDF("id", "maps")
data.groupBy("id").agg(collect_list(data("maps")).alias("maps")).show(false)
输出:
+---+---------------------------------------+
|id |maps |
+---+---------------------------------------+
|1 |[Map(1 -> a), Map(2 -> b), Map(3 -> c)]|
|2 |[Map(1 -> a), Map(2 -> b), Map(3 -> c)]|
+---+---------------------------------------+
答案 2 :(得分:0)
我同意@Shankar。你的代码似乎完美无瑕。
我认为你唯一的错误就是导入了错误的库。
您必须导入
import org.apache.spark.sql.functions.collect_list
但我想你正在导入
import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFCollectList
我希望我猜对了。
答案 3 :(得分:0)
从 Spark 3.0 开始,您可以:
map_entries
将您的地图转换为地图条目数组collect_set
flatten
扁平化收集的数组数组map_from_entries
请参阅以下代码片段,其中 input
是您的输入数据框:
import org.apache.spark.sql.functions.{col, collect_set, flatten, map_entries, map_from_entries}
input
.withColumn("cMap", map_entries(col("cMap")))
.groupBy("id")
.agg(map_from_entries(flatten(collect_set("cMap"))).as("cMap"))
给定以下数据帧输入:
+---+--------------------+
|id |cMap |
+---+--------------------+
|1 |[k1 -> v1] |
|1 |[k2 -> v2, k3 -> v3]|
|2 |[k4 -> v4] |
|2 |[] |
|3 |[k6 -> v6, k7 -> v7]|
+---+--------------------+
上面的代码片段返回以下数据帧:
+---+------------------------------+
|id |cMap |
+---+------------------------------+
|1 |[k1 -> v1, k2 -> v2, k3 -> v3]|
|3 |[k6 -> v6, k7 -> v7] |
|2 |[k4 -> v4] |
+---+------------------------------+