如何在Spark DataFrame SQL中引用用户定义的集合变量

时间:2018-11-09 10:27:05

标签: scala apache-spark apache-spark-sql

我需要允许用户定义不同的命名集合,他们可以在稍后的Spark DataFrame SQL构建中使用它们。

我计划为此目的使用Spark广播变量,但基于以下SO问题How to refer broadcast variable in Spark DataFrameSQL,看来这是不可能的

比方说,作为用户,我已经通过应用程序用户界面创建了以下集合:

name: countries_dict
values: Seq("Italy", "France", "United States", "Poland", "Spain")

在另一个应用程序用户界面(今天与其他页面不同)中,我创建了以下Spark SQL查询:

SELECT name, phone, country FROM users

,我想按SELECT name, phone, country FROM users WHERE countries in countries_dict

过滤记录

例如,现在,我可以通过以下方式创建类似的内容:

val countriesDict = Seq("Italy", "France", "United States", "Poland", "Spain")

val inDict = (s: String) => {
  countriesDict.contains(s)
}

spark.udf.register("in_dict", inDict)

然后:

SELECT name, phone, country FROM users WHERE in_dict(country)

但是这种方法的最大问题是countriesDict被硬编码在代码中,而不是根据UI上的用户输入动态创建的。

是否可以通过某种方式扩展此方法,以通过应用程序UI支持由用户动态创建的具有名称和元素的集合?

2 个答案:

答案 0 :(得分:1)

我当然不知道您的应用程序的UI等,但是有什么反对将集合转换为数据帧的说法吗?当然,您不能使用WHERE countries in countries_dict语法,但必须使用联接。 但是当联接的数据帧低于某个阈值时,Spark将自动以广播形式执行联接。如Mastering Apache Spark

中所述

您只需要一些存储空间,用户就可以在其中存储这些小型数据帧的内容,例如作为CSV文件。

答案 1 :(得分:1)

在这里使用广播变量实际上没有任何意义。即使不理会结构问题,调用udf的成本也可能会超过广播的收益(尤其是在结构如此小的情况下)。

如果数据很小(请使用您喜欢的SQL处理库以避免SQL注入的风险),则可以内联查询:

SELECT name, phone, country FROM users 
WHERE country IN ('Italy', 'France', 'United States', 'Poland', 'Spain')

或仅将输入转换为DataFrame

countriesDict.toDF("country").createOrReplaceTempView("countries")

并使用ANTI JOIN(如果数据足够小,则根据广播阈值自动将其升级为广播连接)

SELECT * 
FROM users LEFT ANTI JOIN countries 
ON users.country = countries.country

或带有明确的广播提示

SELECT  /*+ MAPJOIN(countries) */  * 
FROM users LEFT ANTI JOIN countries 
ON users.country = countries.country

最后,您可以跳过SQL部分,并通过DataFrame使用isin API:

spark.table("users").where($"country" isin (countriesDict: _*))

或者如果您确实有需要UDF的逻辑:

import org.apache.spark.sql.functions.typedLit

val f = udf((x: String, xs: Seq[String]) => { xs.contains(x) })

spark.table("users").where(f($"country", typedLit(countriesDict)))