[英]How to do a time based as-of-join of two datasets in apache spark?
给定S和R的两个数据集都具有时间列(t),如下所述:
//snapshot with id at t
case class S(id: String, t: Int)
//reference data at t
case class R(t: Int, fk: String)
//Example test case
val ss: Dataset[S] = Seq(S("a", 1), S("a", 3), S("b", 5), S("b", 7))
.toDS
val rs: Dataset[R] = Seq(R(0, "a"), R(2, "a"), R(6, "b"))
.toDS
val srs: Dataset[(S, Option[R])] = ss
.asOfJoin(rs)
srs.collect() must contain theSameElementsAs
Seq((S("a", 1), Some(R(0, "a"))), (S("a", 3), Some(R(2, "a"))), (S("b", 5), None), (S("b", 7), Some(R(6, "b"))))
目标是在R中找到与E的ID匹配的最新行,即R在输出中可以是可选的。
asOfJoin
定义如下:
implicit class SOps(ss: Dataset[S]) {
def asOfJoin(rs: Dataset[R])(implicit spark: SparkSession): Dataset[(S, Option[R])] = ???
}
使用数据集API的一种解决方案如下:
def asOfJoin(rs: Dataset[R])(implicit spark: SparkSession): Dataset[(S, Option[R])] = {
import spark.implicits._
ss
.joinWith(
rs,
ss("id") === rs("fk") && ss("t") >= rs("t"),
"left_outer")
.map { case (l, r) => (l, Option(r)) }
.groupByKey { case (s, _) => s }
.reduceGroups { (x, y) =>
(x, y) match {
case ((_, Some(R(tx, _))), (_, Some(R(ty, _)))) => if (tx > ty) x else y
case _ => x
}
}
.map { case (_, r) => r }
}
我不确定数据集S和数据集R的大小。但是从您的代码中,我可以看到join(使用不相等的表达式)的效率很差,并且我可以根据不同的特定情况给出一些建议:
数据集R或数据集S都没有太多数据。
我建议您可以广播较小的数据集,并借助广播变量在spark udf中完成业务逻辑。 这样,您不需要shuffle(join)流程,这可以帮助您节省大量时间和资源。
对于每个唯一ID,count(distinct t)都不大。
我建议您可以通过对id和collect_set(t)进行分组来进行预聚合,如下所示:
select id,collect_set(t) as t_set from S
这样,您可以删除联接中的不等式(ss(“ t”)> = rs(“ t”))。 并使用数据集S和数据集R中的两个t_set编写业务逻辑。
对于其他情况:
我建议您使用相等的联接和窗口函数来优化代码。 由于我对SQL更加熟悉,因此我在此处编写了SQL,可以将其转换为数据集API:
select
sid,
st,
rt
from
(
select
S.id as sid,
S.t as st,
R.t as rt,
row_number() over (partition by S.id order by (S.t - NVL(R.t, 0)) rn
from
S
left join R on S.id = R.fk) tbl
where tbl.rn = 1
我接受了@bupt_ljy的有关避免theta联接的评论,并且跟随似乎可以很好地扩展:
def asOfJoin(rs: Dataset[R])(implicit spark: SparkSession): Dataset[(S, Option[R])] = {
import spark.implicits._
ss
.joinWith(
rs.sort(rs("fk"), rs("t")),
ss("id") === rs("fk"),
"left_outer")
.map { case (l, r) => (l, Option(r)) }
.groupByKey { case (s, _) => s }
.flatMapGroups { (k, vs) =>
new Iterator[(S, Option[R])] {
private var didNotStart: Boolean = true
override def hasNext: Boolean = didNotStart
override def next(): (S, Option[R]) = {
didNotStart = false
vs
.find { case (l, rOpt) =>
rOpt match {
case Some(r) => l.t >= r.t
case _ => false
}
}.getOrElse((k, None))
}
}
}
}
但是,仍然是超级命令性代码,必须有更好的方法...
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.