Spark Sql Dedup行

时间:2016-12-14 12:44:26

标签: apache-spark apache-spark-sql

我们有一个常见的用例,即在行中删除表格。创作顺序。

例如,我们有一个用户操作的事件日志。用户不时标记他最喜欢的类别。 在我们的分析阶段,我们只想知道用户最后喜欢的类别。

示例数据:

id  action_type value date 
123 fav_category 1    2016-02-01
123 fav_category 4    2016-02-02
123 fav_category 8    2016-02-03
123 fav_category 2    2016-02-04

我们希望根据日期列仅获取最新更新。我们当然可以在sql中执行:

select * from (
  select *, row_number() over (
      partition by id,action_type order by date desc) as rnum from tbl
  ) 
where rnum=1;

但是,然而,它并没有在映射器方面进行部分聚合,我们会将所有数据改组为减速器。

我已针对此问题SPARK-17662发布了一个Jira,并且它以更好的SQL样式建议结束:

select id,
       action_type,
       max(struct(date, *)) last_record
from   tbl
group by id,action_type

虽然这个解决方案更清洁仍然有两个问题:

  1. 如果其中一个字段不可排序(例如map<>)
  2. ,这个技巧就不起作用了
  3. 如果在流程的后面我们只选择了一些字段,我们就不会得到下推谓词来优化我们的流程,而忽略了从开头就不需要的字段。
  4. 我们最终为此编写了一个UDAF,它克服了问题#1,但仍然遇到问题#2。

    有没有人想要更好的解决方案?

2 个答案:

答案 0 :(得分:3)

对于任何想要我们当前解决方案的人。这是UDAF的代码 - 注意我们必须使用一些内部函数,所以我们在org.apache.spark.sql.types包中:

package org.apache.spark.sql.types

case class MaxValueByKey(child1: Expression, child2: Expression) extends DeclarativeAggregate {

  override def children: Seq[Expression] = child1 :: child2 :: Nil

  override def nullable: Boolean = true

  // Return data type.
  override def dataType: DataType = child2.dataType

  // Expected input data type.
  override def inputTypes: Seq[AbstractDataType] = Seq(AnyDataType, AnyDataType)

  override def checkInputDataTypes(): TypeCheckResult =
    TypeUtils.checkForOrderingExpr(child1.dataType, "function max")

  private lazy val max = AttributeReference("max", child1.dataType)()
  private lazy val data = AttributeReference("data", child2.dataType)()

  override lazy val aggBufferAttributes: Seq[AttributeReference] = max :: data :: Nil

  override lazy val initialValues: Seq[Expression] = Seq(
    Literal.create(null, child1.dataType),
    Literal.create(null, child2.dataType)
  )

  override lazy val updateExpressions: Seq[Expression] =
    chooseKeyValue(max, data, child1, child2)

  override lazy val mergeExpressions: Seq[Expression] =
    chooseKeyValue(max.left, data.left, max.right, data.right)

  def chooseKeyValue(key1:Expression, value1: Expression, key2:Expression, value2: Expression) = Seq(
    If(IsNull(key1), key2,  If(IsNull(key2), key1,    If(GreaterThan(key1, key2), key1, key2))),
    If(IsNull(key1), value2, If(IsNull(key2), value1, If(GreaterThan(key1, key2), value1, value2)))
  )

  override lazy val evaluateExpression: AttributeReference = data
}

object SparkMoreUDAFs {
  def maxValueByKey(key: Column, value: Column): Column =
      Column(MaxValueByKey(key.expr, value.expr).toAggregateExpression(false))
}

用法是:

sqlContext.table("tbl").groupBy($"id",$"action_type")
          .agg(SparkMoreUDAFs.maxValueByKey($"date", expr("struct(date,*)")).as("s"))

我不确定它是否非常优雅,但它可以进行地图方面的部分聚合,适用于所有列类型。此外,我认为这个UDAF本身也很有用。

希望它会帮助某人..

答案 1 :(得分:0)

UUDF(看起来相当不错的BTW)在键可排序时有效。这也适用于max(struct(key, value))(如果没有,请告诉我)。地图目前无法订购,我已经完成了一些初步工作(https://github.com/apache/spark/pull/15970),但可能需要更全面的方法。

你能详细说明谓词下推吗?我很想知道那里发生了什么。