Apache spark 使用谓词下推从另一个数据帧筛选数据帧

Apache spark 使用谓词下推从另一个数据帧筛选数据帧,apache-spark,Apache Spark,如何根据我拥有的另一个数据帧将过滤器下推到数据帧读取?基本上,我们希望避免完全读取第二个数据帧,然后进行内部联接。相反,我只想在源代码处提交一个关于读取的过滤器。即使我使用了一个与read包装在一起的内部连接,计划也不会显示它正在被过滤。我觉得肯定有更好的方法来建立这个。使用Spark 2.x,到目前为止,我已经做到了这一点,但我希望避免收集以下列表: // Don't want to do this collect...too slow val idFilter = df1.select

如何根据我拥有的另一个数据帧将过滤器下推到数据帧读取?基本上,我们希望避免完全读取第二个数据帧,然后进行内部联接。相反,我只想在源代码处提交一个关于读取的过滤器。即使我使用了一个与read包装在一起的内部连接,计划也不会显示它正在被过滤。我觉得肯定有更好的方法来建立这个。使用Spark 2.x,到目前为止,我已经做到了这一点,但我希望避免收集以下列表:

//  Don't want to do this collect...too slow
  val idFilter = df1.select("id").distinct().map(r => r.getLong(0)).collect.toList
  val df2: DataFrame = spark.read.format("parquet").load("<path>") 
    .filter($"id".isin(idFilter: _*))
//不想进行此收集…速度太慢
val idFilter=df1.select(“id”).distinct().map(r=>r.getLong(0)).collect.toList
val df2:DataFrame=spark.read.format(“拼花”).load(“”)
.filter($“id”.isin(idFilter:*))

除非您自己实现数据源,否则不能直接使用谓词下推。谓词下推是Spark数据源提供的一种机制,必须由每个数据源单独实现

对于基于文件的数据源,已经有了一种基于磁盘分区的简单机制

考虑以下数据帧:

val df = Seq(("test", "day1"), ("test2", "day2")).toDF("data", "day")
如果我们以以下方式将该数据帧保存到磁盘:

df.write.partitionBy("day").save("/tmp/data")
结果将是以下文件夹结构

tmp -
     |
     | - data - |
                |
                |--day=day1 -|- part1....parquet
                |            |- part2....parquet
                |
                |--day=day2 -|- part1....parquet
                             |- part2....parquet
如果您现在像这样使用此数据源:

spark.read.load("/tmp/data").filter($"day" = "day1").show()
Spark甚至不需要加载文件夹day2的数据,因为不需要它

这是一种谓词下推类型,适用于spark支持的每种标准文件格式

更具体的机制是拼花地板。Parquet是一种基于列的文件格式,这意味着它很容易过滤出列。如果在文件
/tmp/myparquet.parquet
中有3列
a
b
c
的基于拼花地板的文件,请执行以下查询:

spark.read.parquet("/tmp/myparquet.parquet").select("a").show()
将导致内部谓词下推,其中spark仅获取列
a
的数据,而不读取列
b
c
的数据

如果有人对通过实现这一特征建立机制感兴趣:

/**
 * A BaseRelation that can eliminate unneeded columns and filter using selected
 * predicates before producing an RDD containing all matching tuples as Row objects.
 *
 * The actual filter should be the conjunction of all `filters`,
 * i.e. they should be "and" together.
 *
 * The pushed down filters are currently purely an optimization as they will all be evaluated
 * again.  This means it is safe to use them with methods that produce false positives such
 * as filtering partitions based on a bloom filter.
 *
 * @since 1.3.0
 */
@Stable
trait PrunedFilteredScan {
  def buildScan(requiredColumns: Array[String], filters: Array[Filter]): RDD[Row]
}

中可以发现,您的想法会导致处理速度变慢,因为您必须在读取时检查每一行。如果不使用collect,您应该使用leftsemi连接,请参见,例如。但我非常确定,它将首先读取整个数据帧,然后执行连接。我正试图避免这种情况,并将过滤器向下推到源代码。您是如何处理的?我有完全相同的问题,join导致了一个完整的表扫描,而不是从另一个数据集中获取值进行过滤。