在Scala中使用多个谓词递归创建Spark筛选器

在Scala中使用多个谓词递归创建Spark筛选器,scala,apache-spark,Scala,Apache Spark,我有一个包含一组值的数据框 val df = List( (2017, 1, 1234), (2017, 2, 1234), (2017, 3, 1234), (2017, 4, 1234), (2018, 1, 12345), (2018, 2, 12346), (2018, 3, 12347), (2018, 4, 12348) ).toDF("year", "month", "employeeCount") df:org.apache.spark.sql.

我有一个包含一组值的数据框

val df = List(
  (2017, 1, 1234),
  (2017, 2, 1234),
  (2017, 3, 1234),
  (2017, 4, 1234),
  (2018, 1, 12345),
  (2018, 2, 12346),
  (2018, 3, 12347),
  (2018, 4, 12348)
).toDF("year", "month", "employeeCount")
df:org.apache.spark.sql.DataFrame=[year:int,month:int,employeeCount:int]

我想通过(年、月)对列表筛选该数据帧:

我可以很容易地作弊并编写实现它的代码:

df.filter(
  (col("year") === 2018 && col("month") === 1) || 
  (col("year") === 2018 && col("month") === 2)
).show
当然,这并不令人满意,因为
filterValues
可能会改变,我想根据列表中的任何内容来确定


是否可以动态构建我的
filter\u表达式
,然后将其传递给
df.filter(filter\u表达式)
?我不知道怎么做。

您可以随时使用
udf
函数作为

val filterValues = List((2018, 1), (2018, 2))

import org.apache.spark.sql.functions._
def filterUdf = udf((year:Int, month:Int) => filterValues.exists(x => x._1 == year && x._2 == month))

df.filter(filterUdf(col("year"), col("month"))).show(false)
已更新

您评论为

我的意思是,要筛选的列列表(以及相应的值列表)将在运行时从其他地方提供

为此,您还将提供列名列表,因此解决方案如下所示

val filterValues = List((2018, 1), (2018, 2))
val filterColumns = List("year", "month")

import org.apache.spark.sql.functions._
def filterUdf = udf((unknown: Seq[Int]) => filterValues.exists(x => !x.productIterator.toList.zip(unknown).map(y => y._1 == y._2).contains(false)))

df.filter(filterUdf(array(filterColumns.map(col): _*))).show(false)

您始终可以使用
udf
函数作为

val filterValues = List((2018, 1), (2018, 2))

import org.apache.spark.sql.functions._
def filterUdf = udf((year:Int, month:Int) => filterValues.exists(x => x._1 == year && x._2 == month))

df.filter(filterUdf(col("year"), col("month"))).show(false)
已更新

您评论为

我的意思是,要筛选的列列表(以及相应的值列表)将在运行时从其他地方提供

为此,您还将提供列名列表,因此解决方案如下所示

val filterValues = List((2018, 1), (2018, 2))
val filterColumns = List("year", "month")

import org.apache.spark.sql.functions._
def filterUdf = udf((unknown: Seq[Int]) => filterValues.exists(x => !x.productIterator.toList.zip(unknown).map(y => y._1 == y._2).contains(false)))

df.filter(filterUdf(array(filterColumns.map(col): _*))).show(false)
基于:


想象一下,有人在命令行中这样称呼它--filterColumns“year,month”-filterValues“2018 | 12018 | 2”

您可以获得列的列表

val colnames = filterColumns.split(',')
将数据转换为本地
数据集
(需要时添加
模式
):

和半连接:

df.join(filter, colnames, "left_semi").show
// +----+-----+-------------+             
// |year|month|employeeCount|
// +----+-----+-------------+
// |2018|    1|        12345|
// |2018|    2|        12346|
// +----+-----+-------------+
像这样的表达也应该起作用:

import org.apache.spark.sql.functions._

val pred = filterValues
  .split(",")
  .map(x => colnames.zip(x.split('|'))
                    .map { case (c, v) => col(c) === v }
                    .reduce(_ && _))
  .reduce(_ || _)

df.where(pred).show
// +----+-----+-------------+
// |year|month|employeeCount|
// +----+-----+-------------+
// |2018|    1|        12345|
// |2018|    2|        12346|
// +----+-----+-------------+
但如果需要某些类型的铸件,则需要更多的工作。

基于:


想象一下,有人在命令行中这样称呼它--filterColumns“year,month”-filterValues“2018 | 12018 | 2”

您可以获得列的列表

val colnames = filterColumns.split(',')
将数据转换为本地
数据集
(需要时添加
模式
):

和半连接:

df.join(filter, colnames, "left_semi").show
// +----+-----+-------------+             
// |year|month|employeeCount|
// +----+-----+-------------+
// |2018|    1|        12345|
// |2018|    2|        12346|
// +----+-----+-------------+
像这样的表达也应该起作用:

import org.apache.spark.sql.functions._

val pred = filterValues
  .split(",")
  .map(x => colnames.zip(x.split('|'))
                    .map { case (c, v) => col(c) === v }
                    .reduce(_ && _))
  .reduce(_ || _)

df.where(pred).show
// +----+-----+-------------+
// |year|month|employeeCount|
// +----+-----+-------------+
// |2018|    1|        12345|
// |2018|    2|        12346|
// +----+-----+-------------+

但是,如果需要某些类型转换,则需要进行更多的工作。

您可以这样构建过滤器表达式:

val df = List(
  (2017, 1, 1234),
  (2017, 2, 1234),
  (2017, 3, 1234),
  (2017, 4, 1234),
  (2018, 1, 12345),
  (2018, 2, 12346),
  (2018, 3, 12347),
  (2018, 4, 12348)
).toDF("year", "month", "employeeCount")

val filterValues = List((2018, 1), (2018, 2))

val filter_expession = filterValues
  .map{case (y,m) => col("year") === y and col("month") === m}
  .reduce(_ || _)

df
  .filter(filter_expession)
  .show()

+----+-----+-------------+
|year|month|employeeCount|
+----+-----+-------------+
|2018|    1|        12345|
|2018|    2|        12346|
+----+-----+-------------+

您可以按如下方式构建过滤器\表达式:

val df = List(
  (2017, 1, 1234),
  (2017, 2, 1234),
  (2017, 3, 1234),
  (2017, 4, 1234),
  (2018, 1, 12345),
  (2018, 2, 12346),
  (2018, 3, 12347),
  (2018, 4, 12348)
).toDF("year", "month", "employeeCount")

val filterValues = List((2018, 1), (2018, 2))

val filter_expession = filterValues
  .map{case (y,m) => col("year") === y and col("month") === m}
  .reduce(_ || _)

df
  .filter(filter_expession)
  .show()

+----+-----+-------------+
|year|month|employeeCount|
+----+-----+-------------+
|2018|    1|        12345|
|2018|    2|        12346|
+----+-----+-------------+

如果我想说“我要筛选的列列表也是未知的,并且可以有任意数量的列”,这会让事情变得更加困难,我该怎么做呢?怎么可能列名称未知,而您仍然要筛选?您希望基于什么基础进行筛选,您至少必须知道;)很抱歉,没有很好地解释:)我的意思是要筛选的列列表(以及相应的值列表)将在运行时从其他地方提供。您的意思是说,就像您有FilterValue一样,您将有要使用的列名列表?是这样吗?想象一下,如果我想说“我要筛选的列的列表也是未知的,并且可以有任意数量的列”,那么有人从命令行用类似于“年,月”-filterValues“2018 | 12018 | 2”的东西来调用它,我该怎么做呢?怎么可能列名未知而您仍要筛选?您希望基于什么基础进行筛选,您至少必须知道;)很抱歉,没有很好地解释:)我的意思是要筛选的列列表(以及相应的值列表)将在运行时从其他地方提供。您的意思是说,就像您有FilterValue一样,您将有要使用的列名列表?这是正确的吗?想象一下,有人从命令行用类似于“年、月”的东西调用这个函数--filterColumns--filterValues“2018 | 12018 | 2”非常有效,谢谢。我仍然对其他一些假设答案感兴趣,因为它们以不同的方式解决了问题,但非常感谢。事实上,我真的很喜欢你给出的第二个答案。太谢谢你了,太好了,谢谢。我仍然对其他一些假设答案感兴趣,因为它们以不同的方式解决了问题,但非常感谢。事实上,我真的很喜欢你给出的第二个答案。非常感谢你。