如何使用数组连接和减少两个数据集?

时间:2017-06-29 16:27:34

标签: scala apache-spark apache-spark-sql

我需要了解如何连接具有数百万个数组的两个数据集。每个数据集的长数为1-10,000,000。但是每个人都有不同的分组。 [1,2] [3,4]和[1],[2,3],[4]输出应为[1,2,3,4] 我需要一些方法来有效地加入这些集合。

我尝试了一种方法,我多次爆炸和分组,最后排序和区分数组。这适用于小集合,但对于大集合非常无效,因为它会多次爆发行数。

有关如何使用其他方法(如reducer或聚合)更有效地解决此问题的任何想法。

以下是scala代码示例。但是,我需要一种适用于java的方法。

val rdd1 = spark.sparkContext.makeRDD(Array("""{"groupings":[1,2,3]}""", """{"groupings":[4,5,6]}""", """{"groupings":[7,8,9]}""", """{"groupings":[10]}""", """{"groupings":[11]}"""))
val rdd2 = spark.sparkContext.makeRDD(Array("""{"groupings":[1]}""", """{"groupings":[2,3,4]}""", """{"groupings":[7,8]}""", """{"groupings":[9]}""", """{"groupings":[10,11]}"""))
val srdd1 = spark.read.json(rdd1)
val srdd2 = spark.read.json(rdd2)

Dataset 1:
+---------+
|groupings|
+---------+
|[1, 2, 3]|
|[4, 5, 6]|
|[7, 8, 9]|
|     [10]|
|     [11]|
+---------+

Dataset 2:
+---------+
|groupings|
+---------+
|      [1]|
|[2, 3, 4]|
|   [7, 8]|
|      [9]|
| [10, 11]|
+---------+

输出应为

+------------------+
|         groupings|
+------------------+
|[1, 2, 3, 4, 5, 6]|
|         [7, 8, 9]|
|          [10, 11]|
+------------------+

更新: 这是我的原始代码,我运行时遇到了问题,@ AyanGuha让我想到,或许只使用一系列连接会更简单,我现在正在测试它,并且如果有效的话会发布一个解决方案。

srdd1.union(srdd2).withColumn("temp", explode(col("groupings")))
                    .groupBy("temp")
                    .agg(collect_list("groupings").alias("groupings"))
                    .withColumn("groupings", callUDF("distinctLongArray", callUDF("flattenDistinctLongArray", col("groupings"))))
                    .withColumn("temp", explode(col("groupings")))
                    .groupBy("temp")
                    .agg(collect_list("groupings").alias("groupings"))
                    .withColumn("groupings", callUDF("distinctLongArray", callUDF("flattenDistinctLongArray", col("groupings"))))
                    .withColumn("temp", explode(col("groupings")))
                    .groupBy("temp")
                    .agg(collect_list("groupings").alias("groupings"))
                    .withColumn("groupings", callUDF("distinctLongArray", callUDF("flattenDistinctLongArray", col("groupings"))))
                    .select(callUDF("sortLongArray", col("groupings")).alias("groupings"))
                    .distinct()

此代码显示的是,经过3次迭代后,数据合并,理想情况下,3个连接将执行相同的操作。

更新2: 看起来我有一个新的工作版本,仍然看起来效率低下但我认为这将由spark更好地处理。

val ardd1 = spark.sparkContext.makeRDD(Array("""{"groupings":[1,2,3]}""", """{"groupings":[4,5,6]}""", """{"groupings":[7,8,9]}""", """{"groupings":[10]}""", """{"groupings":[11,12]}""", """{"groupings":[13,14]}"""))
val ardd2 = spark.sparkContext.makeRDD(Array("""{"groupings":[1]}""", """{"groupings":[2,3,4]}""", """{"groupings":[7,8]}""", """{"groupings":[9]}""", """{"groupings":[10,11]}""", """{"groupings":[12,13]}""", """{"groupings":[14, 15]}"""))
var srdd1 = spark.read.json(ardd1)
var srdd2 = spark.read.json(ardd2)

val addUDF = udf((x: Seq[Long], y: Seq[Long]) => if(y == null) x else (x ++ y).distinct.sorted)
val encompassUDF = udf((x: Seq[Long], y: Seq[Long]) => if(x.size == y.size) false else (x diff y).size == 0)
val arrayContainsAndDiffUDF = udf((x: Seq[Long], y: Seq[Long]) => (x.intersect(y).size > 0) && (y diff x).size > 0)
var rdd1 = srdd1
var rdd2 = srdd2.withColumnRenamed("groupings", "groupings2")
for (i <- 1 to 3){
    rdd1 = rdd1.join(rdd2, arrayContainsAndDiffUDF(col("groupings"), col("groupings2")), "left")
        .select(addUDF(col("groupings"), col("groupings2")).alias("groupings"))
        .distinct
        .alias("rdd1")
    rdd2 = rdd1.select(col("groupings").alias("groupings2")).alias("rdd2")
}
rdd1.join(rdd2, encompassUDF(col("groupings"), col("groupings2")), "leftanti")
    .show(10, false)

输出:

+------------------------+
|groupings               |
+------------------------+
|[10, 11, 12, 13, 14, 15]|
|[1, 2, 3, 4, 5, 6]      |
|[7, 8, 9]               |
+------------------------+

我会大规模地尝试这个,看看我得到了什么。

3 个答案:

答案 0 :(得分:0)

  

这适用于小集,但对于大集非常无效,因为它会多次爆炸行数。

我认为除了explode数组join后面跟distinct有其他选项。 Spark在这样的计算上相当不错,并尝试使用内部二进制行尽可能多地执行它们。压缩数据集,通常在字节级(JVM外部)进行比较

这只是一个足够的记忆力来容纳所有可能没什么大不了的元素。

我建议您试试解决方案并查看实际计划和统计数据。它最终可能成为唯一可用的解决方案。

答案 1 :(得分:0)

以下是使用作为HiveQL一部分支持的ARRAY数据类型的替代解决方案。这至少会使你的编码变得简单[即构建逻辑]。下面的代码假定原始数据位于文本文件中。

步骤1.创建表

create table array_table1(array_col1 array<int>)  ROW FORMAT DELIMITED 
FIELDS TERMINATED BY ',' COLLECTION ITEMS TERMINATED BY ',' LINES TERMINATED 
BY'\n' STORED AS text;

第2步:将数据加载到两个表中

LOAD DATA INPATH('/path/to/file') OVERWRITE INTO TABLE array_table1

步骤3:应用sql函数获取结果

select distinct(explode(array_col1)) from array_table1 union 
select distinct(explode(array_col2)) from array_table2

从示例中我不清楚您要查找的最终输出是什么。它只是所有不同数字的联合 - 或者它们是否应该进行分组?但无论如何,使用创建的表,您可以使用distinct,explode(),left anti join和union的组合来获得预期的结果。

您可能希望优化此代码,以便为重复项再次过滤最终数据集。

希望有所帮助!

答案 2 :(得分:0)

好的,我终于明白了。

首先,我的数组加入我做了一些非常错误的事情,我最初忽略了这一点。

连接具有等效性的两个数组时。 EX。 [1,2,3]等于[1,2,3]吗?数组是哈希的。我正在使用UDF进行交叉匹配。给定[1,2,3]中的x是[1,2,3,4,5]中的任何x。这不能进行哈希处理,因此需要一个计划来检查每一行的每一行。

要做到这一点,你必须首先爆炸两个数组,然后加入它们。 然后,您可以应用其他条件。例如,我通过仅连接不相等的数组来节省时间,并且总和的数量少于另一个。

自我加入的示例:

    rdd2 = rdd2.withColumn("single", explode(col("grouping"))) // Explode the grouping
    temp = rdd2.withColumnRenamed("grouping", "grouping2").alias("temp") // Alias for self join
    rdd2 = rdd2.join(temp, rdd2.col("single").equalTo(temp.col("single")) // Compare singles which will be hashed
                                .and(col("grouping").notEqual(col("grouping2"))) // Apply further conditions
                                .and(callUDF("lessThanArray", col("grouping"), col("grouping2"))) // Make it so only [1,2,3] [4,5,6] is joined and not the duplicate [4,5,6] [1,2,3], for efficiency
                                    , "left") // Left so that the efficiency criteria do not drop rows

然后我按照加入的分组进行分组,反对聚合来自自联接的分组。

rdd2.groupBy("grouping")
    .agg(callUDF("list_agg",col('grouping2')).alias('grouping2')) // List agg is a UserDefinedAggregateFunction which aggregates lists into a distinct list
    .select(callUDF("addArray", col("grouping"), col("grouping2")).alias("grouping")) // AddArray is a udf which concats and distincts 2 arrays
grouping   grouping2
[1,2,3]    [3,4,5]
[1,2,3]    [2,6,7]
[1,2,3]    [2,8,9]
becomes just
[1,2,3]    [2,3,4,5,6,7,8,9]
after addArray
[1,2,3,4,5,6,7,8,9]

然后,我将代码重复了3次,这似乎可以使所有内容合并,并提供一个独特的好措施。

从原始问题中注意我有两个数据集,针对我的具体问题,我发现了关于第一组和第二组的一些假设。我可以假设的第一组没有重复,因为它是主列表,第二组有重复,因此我只需要将上面的代码应用到第二组,然后将其与第一组连接。我假设如果两个集合都有重复,那么它们可以先组合在一起。