Funções definidas pelo utilizador – Scala

Este artigo contém exemplos de função definida pelo utilizador Scala (UDF). Mostra como registar UDFs, como invocar UDFs, e ressalvas sobre a ordem de avaliação de subexpressões em Spark SQL.

Registar uma função como UDF

val squared = (s: Long) => {
  s * s
}
spark.udf.register("square", squared)

Ligue para o UDF em Spark SQL

spark.range(1, 20).createOrReplaceTempView("test")
%sql select id, square(id) as id_squared from test

Utilizar UDF com DataFrames

import org.apache.spark.sql.functions.{col, udf}
val squared = udf((s: Long) => s * s)
display(spark.range(1, 20).select(squared(col("id")) as "id_squared"))

Ordem de avaliação e verificação nulo

O Spark SQL (incluindo o SQL e as APIs dataframe e dataset) não garante a ordem de avaliação das subexpressões. Em especial, as entradas de um operador ou função não são necessariamente avaliadas da esquerda para a direita ou em qualquer outra ordem fixa. Por exemplo, as AND OR expressões lógicas e as expressões não têm semântica de "curto-circuito" da esquerda para a direita.

Por conseguinte, é perigoso confiar nos efeitos colaterais ou na ordem de avaliação das expressões booleanas, e na ordem WHERE e HAVING cláusulas, uma vez que tais expressões e cláusulas podem ser reordenadas durante a otimização e planeamento de consultas. Especificamente, se um UDF depender da semântica de curto-circuito em SQL para verificação nula, não há garantia de que o cheque nulo ocorrerá antes de invocar o UDF. Por exemplo,

spark.udf.register("strlen", (s: String) => s.length)
spark.sql("select s from test1 where s is not null and strlen(s) > 1") // no guarantee

Esta WHERE cláusula não garante que o strlen UDF seja invocado após a filtragem dos nulos.

Para efetuar uma verificação nudificante adequada, recomendamos que faça uma das seguintes:

  • Faça o próprio UDF deserdá-lo e faça a verificação nula dentro do próprio UDF
  • Utilização IF ou CASE WHEN expressões para fazer o cheque nulo e invocar o UDF num ramo condicional
spark.udf.register("strlen_nullsafe", (s: String) => if (s != null) s.length else -1)
spark.sql("select s from test1 where s is not null and strlen_nullsafe(s) > 1") // ok
spark.sql("select s from test1 where if(s is not null, strlen(s), null) > 1")   // ok