在用于Azure Databricks的Spark-SQL中创建用户定义的(不是临时的)函数

时间:2019-07-10 06:05:51

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

也许是愚蠢的,我是Microsoft SQL / C#开发人员,以前从未真正使用过任何其他IDE /编写过JAVA / SCALA。 我正在将一些Azure SQL查询迁移到Azure Databricks解决方案。

似乎没有等效的TSQL DATEDIFF_BIG函数(https://docs.microsoft.com/en-us/sql/t-sql/functions/datediff-transact-sql?view=sql-server-2017

找到的解决方案是-编写自己的UDF。

我在SCALA笔记本中完成了以下操作(请参阅下文),该功能可以很好地用于临时功能。 (https://docs.databricks.com/spark/latest/spark-sql/language-manual/create-function.html

这是我发现的https://github.com/johnmuller87/spark-udf最有用的示例。

有很多临时函数示例,但没有找到非JAVA / SCALA开发人员的永久函数示例。

我安装了SBT(Windows的最新版本-https://www.scala-sbt.org/1.x/docs/Installing-sbt-on-Windows.html) 我还安装了Intellj

我为IBAN示例运行了SBT BUILT,但是将JAR升级到我的集群后,我无法获得SQL函数,并且函数注册可以正常工作。

CREATE FUNCTION ValidateIBAN AS 'com.ing.wbaa.spark.udf.ValidateIBAN' USING JAR 'spark_udf_assembly_0_2_0' --without extension

SELECT ValidateIBAN('NL20INGB0001234567')

错误总是 “ SQL语句中的错误:AnalysisException:没有UDF / UDAF / UDTF'com.ing.wbaa.spark.udf.ValidateIBAN'的处理程序;第1行pos 7”

//import org.apache.spark.sql.types._                         // include the Spark Types to define our schema
import org.apache.spark.sql.types.LongType
import org.apache.spark.sql.functions.udf
import java.time.temporal.ChronoUnit;

// Define function to calculate local time offset
def getTimestampDifference(interval: java.lang.String, date1: java.sql.Timestamp, date2: java.sql.Timestamp) : java.lang.Long = {

  //https://docs.oracle.com/javase/8/docs/api/java/sql/Timestamp.html
  //https://spark.apache.org/docs/2.4.0/sql-reference.html
  //https://alvinalexander.com/scala/how-to-use-scala-match-expression-like-switch-case-statement

  interval match
  {
    case "NANOSECOND"=> return ChronoUnit.NANOS.between(date1.toInstant(), date2.toInstant());
    case "MICROSECOND"=> return ChronoUnit.MICROS.between(date1.toInstant(), date2.toInstant());
    case "MILLISECOND"=> return ChronoUnit.MILLIS.between(date1.toInstant(), date2.toInstant()); // date2.getTime() - date1.getTime();
    case "SECOND"=> return ChronoUnit.SECONDS.between(date1.toInstant(), date2.toInstant());
    case "MINUTE"=> return ChronoUnit.MINUTES.between(date1.toInstant(), date2.toInstant());
    case "HOUR"=> return ChronoUnit.HOURS.between(date1.toInstant(), date2.toInstant());
    case "DAY"=> return ChronoUnit.DAYS.between(date1.toInstant(), date2.toInstant());
    case "WEEK"=> return ChronoUnit.WEEKS.between(date1.toInstant(), date2.toInstant());
    case "MONTH"=> return ChronoUnit.MONTHS.between(date1.toInstant(), date2.toInstant());
    case "YEAR"=> return ChronoUnit.YEARS.between(date1.toInstant(), date2.toInstant());
  }
}

spark.udf.register("DATETIMEDIFF", udf(getTimestampDifference(_:java.lang.String, _:java.sql.Timestamp,_:java.sql.Timestamp),LongType))

我真正需要的是-如何将SCALA Notebook转换为SQL函数,以便可以在以下位置的永久SQL视图中使用它 Azure Databricks群集版本5.4(包括Apache Spark 2.4.3,Scala 2.11)

  • 要实现什么类
  • 要实现的方法(在C#中覆盖)-关于HIVE或SPARK的文章也不同
  • 如何在Java存档中设置SBT Built或以其他方式编译它,以便我可以成功创建和运行SQL函数(仅在SQL中,在pyhton代码中或在scala代码中-在SQL Notebook中)

感谢您的帮助

2 个答案:

答案 0 :(得分:1)

Spark不会为您提供任何持续单个火花会话(Databricks - Creating permanent User Defined Functions (UDFs)或Databricks术语中的集群生存期)的永久功能。如果您需要长时间运行的Spark会话(仅SQL部分),可以考虑将这些UDF添加到Hive并从Spark调用它们。否则(考虑瞬态群集),每次启动群集时都需要重新添加它。

您的UDF代码不是最佳的:不处理空/空值,否则将引发异常

对于基本(标准)火花UDF,请参见https://jaceklaskowski.gitbooks.io/mastering-spark-sql/spark-sql-udfs.html,不需要真正的接口(与Hive不同)

关于: SQL函数(仅在SQL中)/ SBT:

如果您真的需要它(对于这个简单的用例),https://github.com/geoHeil/sparkSimpleProjectTemplate.g8可能是您的一个示例。

但是对于此代码,不需要其他依赖项。创建一个文本/ Scala文件就足够了,其中包含功能所需的<100行代码。 然后可以使用API​​在集群创建时调用此文件(“笔记本”?),即通过https://docs.databricks.com/user-guide/dev-tools/databricks-cli.html和某些脚本来调用,因此就像永久文件一样。

此外: 始终考虑使用Spark原生(催化剂优化)功能。 DATEDIFF in SPARK SQl常规datediff可能已经完成了datediff-big需要完成的许多工作,并且减去了简单的时间戳类型列。 如果我一眼就可以正确地理解它,那么只会将输出格式化为所需的粒度(即可以从t-SQL函数中直接获得),可以通过将其嵌套在不同的函数中来实现,例如:

  • 或手动划分返回的差异

答案 1 :(得分:0)

您所引用的Databricks中的CREATE FUNCTION语句实际上是一个Hive命令,而不是Spark,它期望UDF类是Hive UDF。

这也是您收到“没有UDF / UDAF / UDTF处理程序”错误的原因。您链接的示例实现了 Spark UDF ,而您需要实现的是 Hive UDF

要创建Hive UDF,您需要实现一个类,该类扩展了类org.apache.hadoop.hive.ql.exec.UDF并实现了一个称为评估的函数。在您的情况下,整个类应如下所示:

class GetTimestampDifference extends UDF {

  def evaluate(interval: java.lang.String, date1: java.sql.Timestamp, date2: java.sql.Timestamp) : java.lang.Long = {

  //https://docs.oracle.com/javase/8/docs/api/java/sql/Timestamp.html
  //https://spark.apache.org/docs/2.4.0/sql-reference.html
  //https://alvinalexander.com/scala/how-to-use-scala-match-expression-like-switch-case-statement

  interval match
  {
    case "NANOSECOND"=> return ChronoUnit.NANOS.between(date1.toInstant(), date2.toInstant());
    case "MICROSECOND"=> return ChronoUnit.MICROS.between(date1.toInstant(), date2.toInstant());
    case "MILLISECOND"=> return ChronoUnit.MILLIS.between(date1.toInstant(), date2.toInstant()); // date2.getTime() - date1.getTime();
    case "SECOND"=> return ChronoUnit.SECONDS.between(date1.toInstant(), date2.toInstant());
    case "MINUTE"=> return ChronoUnit.MINUTES.between(date1.toInstant(), date2.toInstant());
    case "HOUR"=> return ChronoUnit.HOURS.between(date1.toInstant(), date2.toInstant());
    case "DAY"=> return ChronoUnit.DAYS.between(date1.toInstant(), date2.toInstant());
    case "WEEK"=> return ChronoUnit.WEEKS.between(date1.toInstant(), date2.toInstant());
    case "MONTH"=> return ChronoUnit.MONTHS.between(date1.toInstant(), date2.toInstant());
    case "YEAR"=> return ChronoUnit.YEARS.between(date1.toInstant(), date2.toInstant());
  }
}

}

然后,您需要将其编译为一个JAR文件,将其复制到databricks文件系统中的某个位置,并使用与之前相同的命令来创建永久功能(假设您保留了IBAN示例的名称空间):

CREATE FUNCTION GetTimestampDifference AS 'com.ing.wbaa.spark.udf.GetTimestampDifference' USING JAR '[path to your jar in dbfs]'

SELECT GetTimestampDifference ("MILLISECOND",cast("2019-07-08 16:07:03.246" as timestamp), cast("2019-07-08 16:07:03.248" as timestamp))

假设您仍在修改开始的IBAN示例项目,为了创建jar文件,必须将以下程序包依赖项添加到build.sbt文件中:

"org.apache.spark" %% "spark-hive" % "2.4.3"
相关问题