Dataframe 在apachesparksql中,如何在使用collect_list In window函数时删除重复的行?

Dataframe 在apachesparksql中,如何在使用collect_list In window函数时删除重复的行?,dataframe,apache-spark,apache-spark-sql,Dataframe,Apache Spark,Apache Spark Sql,我有下面的数据框 +----+-----+----+--------+ |year|month|item|quantity| +----+-----+----+--------+ |2019|1 |TV |8 | |2019|2 |AC |10 | |2018|1 |TV |2 | |2018|2 |AC |3 | +----+-----+----+--------+ 通过使用窗口函数,我希望得到以下输出 val p

我有下面的数据框

+----+-----+----+--------+
|year|month|item|quantity|
+----+-----+----+--------+
|2019|1    |TV  |8       |
|2019|2    |AC  |10      |
|2018|1    |TV  |2       |
|2018|2    |AC  |3       |
+----+-----+----+--------+
通过使用窗口函数,我希望得到以下输出

val partitionWindow = Window.partitionBy("year").orderBy("month")
val itemsList= collect_list(struct("item", "quantity")).over(partitionWindow)

df.select("year", itemsList as "items")

Expected output:
+----+-------------------+
|year|items              |
+----+-------------------+
|2019|[[TV, 8], [AC, 10]]|
|2018|[[TV, 2], [AC, 3]] |
+----+-------------------+
但是,当我使用窗口函数时,每个项目都有重复的行

Current output:
+----+-------------------+
|year|items              |
+----+-------------------+
|2019|[[TV, 8]]          |
|2019|[[TV, 8], [AC, 10]]|
|2018|[[TV, 2]]          |
|2018|[[TV, 2], [AC, 3]] |
+----+-------------------+

我想知道哪种方法是删除重复行的最佳方法?

起初,我在寻找一种没有UDF的方法。除了一个我无法优雅地解决的方面外,这还可以。使用简单的映射自定义项,它非常简单,比其他答案更简单。所以,为了子孙后代,再后来由于其他的承诺

试试这个

import spark.implicits._
import org.apache.spark.sql.functions._

case class abc(year: Int, month: Int, item: String, quantity: Int)
val itemsList= collect_list(struct("month", "item", "quantity"))

val my_udf = udf { items: Seq[Row] =>  
                   val res = items.map { r => (r.getAs[String](1), r.getAs[Int](2)) }
                   res
                 }
// Gen some data, however, not the thrust of the problem.
val df0 = Seq(abc(2019, 1, "TV", 8), abc(2019, 7, "AC", 10), abc(2018, 1, "TV", 2), abc(2018, 2, "AC", 3), abc(2019, 2, "CO", 7)).toDS()
val df1 = df0.toDF()

val df2 = df1.groupBy($"year")
             .agg(itemsList as "items")
             .withColumn("sortedCol", sort_array($"items", asc = true))
             .withColumn("sortedItems", my_udf(col("sortedCol") ))
             .drop("items").drop("sortedCol")
             .orderBy($"year".desc)

df2.show(false)
df2.printSchema()
注意您应该修复的以下问题:

  • 晚一点订货比较好
  • 数据错误(现已修复)
  • 按字符串排序mth不是一个好主意,需要转换为mth num
返回:

+----+----------------------------+
|year|sortedItems                 |
+----+----------------------------+
|2019|[[TV, 8], [CO, 7], [AC, 10]]|
|2018|[[TV, 2], [AC, 3]]          |
+----+----------------------------+

我相信这里有趣的部分是,汇总的项目列表是按月份排序的。因此,我用以下三种方法编写了代码:

创建示例数据集:

import org.apache.spark.sql._
import org.apache.spark.sql.functions._
case class data(year : Int, month : Int, item : String, quantity : Int)
val spark = SparkSession.builder().master("local").getOrCreate()
import spark.implicits._
val inputDF = spark.createDataset(Seq(
    data(2018, 2, "AC", 3),
    data(2019, 2, "AC", 10),
    data(2019, 1, "TV", 2),
    data(2018, 1, "TV", 2)
    )).toDF()
方法1:将月份、项目和数量汇总到列表中,然后使用UDF按月份对项目进行排序,如下所示:

case class items(item : String, quantity : Int)
def getItemsSortedByMonth(itemsRows : Seq[Row]) : Seq[items] = {
    if (itemsRows == null || itemsRows.isEmpty) {
      null
    }
    else {
      itemsRows.sortBy(r => r.getAs[Int]("month"))
      .map(r => items(r.getAs[String]("item"), r.getAs[Int]("quantity")))
    }
  }
val itemsSortedByMonthUDF = udf(getItemsSortedByMonth(_: Seq[Row]))
val outputDF = inputDF.groupBy(col("year"))
    .agg(collect_list(struct("month", "item", "quantity")).as("items"))
    .withColumn("items", itemsSortedByMonthUDF(col("items")))
方法2:使用窗口函数

val monthWindowSpec = Window.partitionBy("year").orderBy("month")
       val rowNumberWindowSpec = Window.partitionBy("year").orderBy("row_number")
        val runningList = collect_list(struct("item", "quantity")). over(rowNumberWindowSpec)
    val tempDF = inputDF
      // using row_number for continuous ranks if there are multiple items in the same month
      .withColumn("row_number", row_number().over(monthWindowSpec))
      .withColumn("items", runningList)
    .drop("month", "item", "quantity")

    tempDF.persist()
    val yearToSelect = tempDF.groupBy("year").agg(max("row_number").as("row_number"))

    val outputDF = tempDF.join(yearToSelect, Seq("year", "row_number")).drop("row_number")
编辑: 使用Dataset API的-groupByKey和mapGroups为后代添加了第三种方法:

//encoding to data class can be avoided if inputDF is not converted dataset of row objects
val outputDF = inputDF.as[data].groupByKey(_.year).mapGroups{ case (year, rows) =>
      val itemsSortedByMonth = rows.toSeq.sortBy(_.month).map(s => items(s.item, s.quantity))
      (year, itemsSortedByMonth)
    }.toDF("year", "items")

我认为,collect_set将删除数据集中的重复项,而不是重复行。请注意答案处的输出和groupBy,同时有collect_set和collect_List对不起,对于数据集中的错误,我现在已经更正了。我已尝试运行您共享的代码,但我遇到以下错误org.apache.spark.sql.AnalysisException:表达式“
item
”既不在group by中,也不是聚合函数。添加到分组依据或换行第一()(对我有用。将你的cde全部粘贴到下面,我会检查我认为你错过了分区窗口…..在我阅读的一些博客中,使用group and collect_list可能没有正确地遵循顺序..纠正了数据集中的错误u可以使用
无界窗口上的
last
函数进行过滤。或者获取
行数()
然后过滤
行数的
max
。这是一个非常好的问题,非udf解决方案,我用1给出了一个udf非常简单的答案。好东西,是试图避免udf,第二个不确定我会想出。我尝试了第三种方法,效果很好。谢谢解决方案谢谢,@Bluephantom、 有什么理由避免UDF吗?@s1705为后代添加了第三种方法代码。很高兴它对你有用:)也给出了更简单的答案。