Java Spark SQL:在数组值上使用collect_set?

Java Spark SQL:在数组值上使用collect_set?,java,apache-spark,apache-spark-sql,Java,Apache Spark,Apache Spark Sql,我有一个聚合数据框,其中有一列是使用collect\u set创建的。现在我需要再次在此数据帧上进行聚合,并再次将collect\u set应用于该列的值。问题是,我需要对集合的值应用collect\u Set,目前为止,我看到的唯一方法是分解聚合的数据帧。有更好的办法吗 示例: 初始数据帧: country | continent | attributes ------------------------------------- Canada | America | A

我有一个聚合数据框,其中有一列是使用
collect\u set
创建的。现在我需要再次在此数据帧上进行聚合,并再次将
collect\u set
应用于该列的值。问题是,我需要对集合的值应用
collect\u Set
,目前为止,我看到的唯一方法是分解聚合的数据帧。有更好的办法吗

示例:

初始数据帧:

country   | continent   | attributes
-------------------------------------
Canada    | America     | A
Belgium   | Europe      | Z
USA       | America     | A
Canada    | America     | B
France    | Europe      | Y
France    | Europe      | X
聚合数据帧(我作为输入接收的数据帧)-在
国家/地区聚合:

country   | continent   | attributes
-------------------------------------
Canada    | America     | A, B
Belgium   | Europe      | Z
USA       | America     | A
France    | Europe      | Y, X
我所需的输出-在
大陆上聚合

continent   | attributes
-------------------------------------
America     | A, B
Europe      | X, Y, Z

由于此时只能有少量行,因此只需按原样收集属性并展平结果(Spark>=2.4)

+---------+----------+
|大陆|属性|
+---------+----------+
|欧洲|[Y,X,Z]|
|美国|[A,B]|
+---------+----------+
在一般情况下,事情要难处理得多,在许多情况下,如果您希望有大的列表,每个组有许多重复项和许多值,那么最佳解决方案*就是从头开始重新计算结果,即

input.groupBy($"continent").agg(collect_set($"attributes") as "attributes")
一种可能的替代方法是使用
聚合器

import org.apache.spark.sql.expressions.Aggregator
import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder
import org.apache.spark.sql.{Encoder, Encoders}
import scala.collection.mutable.{Set => MSet}


class MergeSets[T, U](f: T => Seq[U])(implicit enc: Encoder[Seq[U]]) extends 
     Aggregator[T, MSet[U], Seq[U]] with Serializable {

  def zero = MSet.empty[U]

  def reduce(acc: MSet[U], x: T) = {
    for { v <- f(x) } acc.add(v)
    acc
  }

  def merge(acc1: MSet[U], acc2: MSet[U]) = {
    acc1 ++= acc2
  }

  def finish(acc: MSet[U]) = acc.toSeq
  def bufferEncoder: Encoder[MSet[U]] = Encoders.kryo[MSet[U]]
  def outputEncoder: Encoder[Seq[U]] = enc

}
+---------+----------+
|大陆|属性|
+---------+----------+
|欧洲|[X,Y,Z]|
|美国|[B,A]|
+---------+----------+
但这显然不是一个对Java友好的选项

另请参见(类似,但没有唯一性约束)



*这是因为
explode
可能非常昂贵,尤其是在较旧的Spark版本中,与访问SQL集合的外部表示形式一样。

我想知道如何在pyspark中实现相同的结果?
import org.apache.spark.sql.expressions.Aggregator
import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder
import org.apache.spark.sql.{Encoder, Encoders}
import scala.collection.mutable.{Set => MSet}


class MergeSets[T, U](f: T => Seq[U])(implicit enc: Encoder[Seq[U]]) extends 
     Aggregator[T, MSet[U], Seq[U]] with Serializable {

  def zero = MSet.empty[U]

  def reduce(acc: MSet[U], x: T) = {
    for { v <- f(x) } acc.add(v)
    acc
  }

  def merge(acc1: MSet[U], acc2: MSet[U]) = {
    acc1 ++= acc2
  }

  def finish(acc: MSet[U]) = acc.toSeq
  def bufferEncoder: Encoder[MSet[U]] = Encoders.kryo[MSet[U]]
  def outputEncoder: Encoder[Seq[U]] = enc

}
case class CountryAggregate(
  country: String, continent: String, attributes: Seq[String])

byState
  .as[CountryAggregate]
  .groupByKey(_.continent)
  .agg(new MergeSets[CountryAggregate, String](_.attributes).toColumn)
  .toDF("continent", "attributes")
  .show