我们有一个常见的用例,即在行中删除表格。创作顺序。
例如,我们有一个用户操作的事件日志。用户不时标记他最喜欢的类别。 在我们的分析阶段,我们只想知道用户最后喜欢的类别。
示例数据:
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
虽然这个解决方案更清洁仍然有两个问题:
我们最终为此编写了一个UDAF,它克服了问题#1,但仍然遇到问题#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),但可能需要更全面的方法。
你能详细说明谓词下推吗?我很想知道那里发生了什么。