Apache spark 基于指定denylist条件的另一个数据帧筛选Spark数据帧

Apache spark 基于指定denylist条件的另一个数据帧筛选Spark数据帧,apache-spark,dataframe,apache-spark-sql,Apache Spark,Dataframe,Apache Spark Sql,我有一个大的DataFrame多列和数十亿行,还有一个小的DataFrame单列和10000行 每当largeDataFrame中的some_identifier列与smallDataFrame中的一行匹配时,我就要过滤largeDataFrame中的所有行 下面是一个例子: 大数据帧 some_idenfitier,first_name 111,bob 123,phil 222,mary 456,sue 小型数据帧 some_identifier 123 456 期望输出 111,bob

我有一个大的DataFrame多列和数十亿行,还有一个小的DataFrame单列和10000行

每当largeDataFrame中的some_identifier列与smallDataFrame中的一行匹配时,我就要过滤largeDataFrame中的所有行

下面是一个例子:

大数据帧

some_idenfitier,first_name
111,bob
123,phil
222,mary
456,sue
小型数据帧

some_identifier
123
456
期望输出

111,bob
222,mary
这是我丑陋的解决方案

val smallDataFrame2 = smallDataFrame.withColumn("is_bad", lit("bad_row"))
val desiredOutput = largeDataFrame.join(broadcast(smallDataFrame2), Seq("some_identifier"), "left").filter($"is_bad".isNull).drop("is_bad")
有更干净的解决方案吗?

在这种情况下,您需要使用左反联接

左反联接与左半联接相反

它根据给定的键从左表的右表中过滤出数据:

largeDataFrame
   .join(smallDataFrame, Seq("some_identifier"),"left_anti")
   .show
// +---------------+----------+
// |some_identifier|first_name|
// +---------------+----------+
// |            222|      mary|
// |            111|       bob|
// +---------------+----------+

一个纯Spark SQL版本,以PySpark为例,但有一些小改动 同样适用于Scala API:

def string_到_数据帧df_名称,csv_字符串: rdd=spark.sparkContext.parallelizecsv_string.split\n df=spark.read.option'header','true'。option'inferSchema','true' df.RegisterEmptabledf_名称 字符串\u到\u dataframelargeDataFrame,'某个\u标识符,第一个\u名称 111,鲍勃 123,菲尔 222,玛丽 456,苏的 字符串\u到\u dataframesmallDataFrame,'某个\u标识符 123 456 ' anti_join_df=spark.sql 选择* 从大数据帧L 不存在的地方 从smallDataFrame S中选择1 其中L.some_标识符=S.some_标识符 printanti_join_df.take10 反加入解释 将预期输出mary和bob:

[Rowsome_identifier=222,first_name='mary', Rowsome_identifier=111,first_name='bob']

物理执行计划也将显示它正在使用

== Physical Plan ==
SortMergeJoin [some_identifier#252], [some_identifier#264], LeftAnti
:- *(1) Sort [some_identifier#252 ASC NULLS FIRST], false, 0
:  +- Exchange hashpartitioning(some_identifier#252, 200)
:     +- Scan ExistingRDD[some_identifier#252,first_name#253]
+- *(3) Sort [some_identifier#264 ASC NULLS FIRST], false, 0
   +- Exchange hashpartitioning(some_identifier#264, 200)
      +- *(2) Project [some_identifier#264]
         +- Scan ExistingRDD[some_identifier#264]
请注意,排序合并联接对于联接/反联接大小大致相同的数据集更为有效。 由于您提到小数据帧更小,我们应该确保Spark optimizer选择广播哈希连接,在这种情况下,广播哈希连接将更加有效:

我会将“不存在”更改为“不存在”条款:

anti_join_df=spark.sql 选择* 从大数据帧L 其中L.some_标识符不在 选择S.some\u标识符 从smallDataFrame S 反加入解释 让我们看看它给了我们什么:

== Physical Plan ==
BroadcastNestedLoopJoin BuildRight, LeftAnti, ((some_identifier#302 = some_identifier#314) || isnull((some_identifier#302 = some_identifier#314)))
:- Scan ExistingRDD[some_identifier#302,first_name#303]
+- BroadcastExchange IdentityBroadcastMode
   +- Scan ExistingRDD[some_identifier#314]
请注意,Spark优化器实际上选择了广播嵌套循环联接,而不是广播哈希联接。前者是可以的,因为我们只有两个记录要从左侧排除

还请注意,这两个执行计划都有LeftAnti,因此它类似于@eliasah answer,但使用纯SQL实现。此外,它还表明您可以对物理执行计划进行更多的控制


另外,请记住,如果右侧数据帧比左侧数据帧小得多,但比几个记录大,则您确实希望有广播散列连接,而不是广播嵌套循环连接或排序合并连接。如果这种情况没有发生,您可能需要进行调整,因为它的默认值为10Mb,但它必须大于smallDataFrame的大小。

这就是为什么您在真正需要枚举时不使用字符串的原因-DataSet.join的scaladoc没有提到leftanti作为选项,因此无法确定,有什么选择-不进行深度潜水。我开始觉得这应该是一个Jira——而且之前已经被API的选择激怒了。老实说,当我写下这个答案时,数据集是实验性的,我还不是一个大粉丝。谢天谢地,Jacek有完整的或我希望的关于连接的文档:-希望离开这里可以让其他人的生活更轻松。@RickMoritz链接现在不可用。它应该是left_anti而不是leftanti。