Built with Tutify.io

    AIStudyLabs

    UDF (user Defined Functions)

    UDF (user Defined Functions)

    Introduction

    Spark SQL UDF (a.k.a. User-Defined Function) is a highly useful feature of Spark SQL & DataFrame that extends the Spark built-in capabilities.

    Note: UDFs are the most expensive operations hence use them only you have no choice and when essential.

    What is Spark UDF?

    UDF a.k.a User Defined Function, If you are coming from an SQL background, UDFs are nothing new to you as most of the traditional RDBMS databases support User Defined Functions, and Spark UDFs are similar to these.

    In Spark, you create a UDF by writing a function in a language you prefer to use for Spark. For example, if you are using Spark with Java, you create a UDF in Java language and wrap it with the udf() function or register it as a UDF to use it on DataFrame and SQL respectively.

    Why do we need a Spark UDF?

    UDFs are used to extend the functions of the framework and re-use this function on several DataFrames. For example, if you wanted to convert every first letter of a word in a sentence to a capital case, Spark's built-in features don't have this function, hence you can create it as a UDF and reuse this as needed on many DataFrames. UDFs, once created, can be reused on several DataFrames and SQL expressions.

    Before you create any UDF, do your research to check if the similar function you wanted is already available in Spark SQL Functions. Spark SQL provides several predefined common functions and many more new functions are added with every release. Hence, it is best to check before you reinvent the wheel.

    When creating UDFs, you need to design them very carefully; otherwise, you will come across performance issues.

    Create a DataFrame

    Before we jump into creating a UDF, first let’s create a Spark DataFrame.

    import org.apache.spark.sql.Dataset;
    import org.apache.spark.sql.Row;
    import org.apache.spark.sql.SparkSession;
    import org.apache.spark.sql.functions;
    import java.util.Arrays;
    import java.util.List;
    
    SparkSession spark = SparkSession.builder().appName("UDF Example").master("local").getOrCreate();
    
    List<Row> data = Arrays.asList(
      RowFactory.create("1", "Be the change that you wish to see in the world"),
      RowFactory.create("2", "Everyone thinks of changing the world, but no one thinks of changing himself."),
      RowFactory.create("3", "The purpose of our lives is to be happy.")
    );
    
    StructType schema = new StructType(new StructField[]{
      new StructField("Seqno", DataTypes.StringType, false, Metadata.empty()),
      new StructField("Quote", DataTypes.StringType, false, Metadata.empty())
    });
    
    Dataset<Row> df = spark.createDataFrame(data, schema);
    df.show(false);
    

    Yields the below output.

    +-----+-----------------------------------------------------------------------------+
    |Seqno|Quote                                                                        |
    +-----+-----------------------------------------------------------------------------+
    |1    |Be the change that you wish to see in the world                              |
    |2    |Everyone thinks of changing the world, but no one thinks of changing himself.|
    |3    |The purpose of our lives is to be happy.                                     |
    +-----+-----------------------------------------------------------------------------+
    

    Create a Function

    The first step in creating a UDF is creating a Java function. Below is a function convertCase() which takes a string parameter and converts the first letter of every word to a capital letter. UDFs take parameters of your choice and return a value.

    import org.apache.spark.api.java.function.MapFunction;
    
    public static String convertCase(String strQuote) {
        String[] arr = strQuote.split(" ");
        StringBuilder builder = new StringBuilder();
        for (String word : arr) {
            builder.append(Character.toUpperCase(word.charAt(0)))
                   .append(word.substring(1)).append(" ");
        }
        return builder.toString().trim();
    }
    

    Create Spark UDF to use it on DataFrame

    Now convert this function convertCase() to a UDF by passing the function to Spark SQL udf(), this function is available in the org.apache.spark.sql.functions package. Make sure you import this package before using it.

    import org.apache.spark.sql.api.java.UDF1;
    import org.apache.spark.sql.expressions.UserDefinedFunction;
    
    UserDefinedFunction convertUDF = functions.udf((UDF1<String, String>) s -> convertCase(s), DataTypes.StringType);
    

    Now you can use convertUDF() on a DataFrame column.

    // Using with DataFrame
    df.select(functions.col("Seqno"), convertUDF.apply(functions.col("Quote")).as("Quote")).show(false);
    

    This results in the below output.

    +-----+-----------------------------------------------------------------------------+
    |Seqno|Quote                                                                        |
    +-----+-----------------------------------------------------------------------------+
    |1    |Be The Change That You Wish To See In The World                              |
    |2    |Everyone Thinks Of Changing The World, But No One Thinks Of Changing Himself.|
    |3    |The Purpose Of Our Lives Is To Be Happy.                                     |
    +-----+-----------------------------------------------------------------------------+
    

    Registering Spark UDF to use it on SQL

    In order to use the convertCase() function on Spark SQL, you need to register the function with Spark using spark.udf().register().

    // Using it on SQL
    spark.udf().register("convertUDF", (UDF1<String, String>) s -> convertCase(s), DataTypes.StringType);
    df.createOrReplaceTempView("QUOTE_TABLE");
    spark.sql("SELECT Seqno, convertUDF(Quote) FROM QUOTE_TABLE").show(false);
    

    This yields the same output as the previous example.

    null check

    UDFs are error-prone when not designed carefully. For example, when you have a column that contains the value null on some records, and not handling null inside a UDF function returns the below error.

    Exception in thread "main" org.apache.spark.SparkException: Failed to execute user defined function(anonfun$1: (string) => string)
        at org.apache.spark.sql.catalyst.expressions.ScalaUDF.eval(ScalaUDF.scala:1066)
        at org.apache.spark.sql.catalyst.expressions.Alias.eval(namedExpressions.scala:152)
        at org.apache.spark.sql.catalyst.expressions.InterpretedMutableProjection.apply(Projection.scala:92)
        at org.apache.spark.sql.catalyst.optimizer.ConvertToLocalRelation$anonfun$apply$24$anonfun$applyOrElse$23.apply(Optimizer.scala:1364)
        at org.apache.spark.sql.catalyst.optimizer.ConvertToLocalRelation$anonfun$apply$24$anonfun$applyOrElse$23.apply(Optimizer.scala:1364)
        at scala.collection.TraversableLike$anonfun$map$1.apply(TraversableLike.scala:234)
        at scala.collection.TraversableLike$anonfun$map$1.apply(TraversableLike.scala:234)
        at scala.collection.immutable.List.foreach(List.scala:392)
        at scala.collection.TraversableLike$class.map(TraversableLike.scala:234)
        at scala.collection.immutable.List.map(List.scala:296)
        at org.apache.spark.sql.catalyst.optimizer.ConvertToLocalRelation$anonfun$apply$24.applyOrElse(Optimizer.scala:1364)
        at org.apache.spark.sql.catalyst.optimizer.ConvertToLocalRelation$anonfun$apply$24.applyOrElse(Optimizer.scala:1359)
    

    It is always best practice to check for null inside a UDF function rather than checking for null outside.

    Performance concern using UDF

    UDFs are a black box to Spark hence it can’t apply optimization and you will lose all the optimization Spark does on DataFrame/Dataset. When possible, you should use Spark SQL built-in functions as these functions provide optimization.

    Apache Spark Tutorial with Java Examples — Advanced DataFrame Features

    Introduction to Apache Spark

    Setup Apache Spark Environment

    SparkSession & SparkContext

    Resilient Distributed Datasets (RDDs)

    Cache, Persistence and Shared Variables

    DataFrame Introduction

    DataFrame Narrow Transformation

    DataFrame Wider Transformation

    DataFrame Actions

    Spark SQL Functions

    Advanced DataFrame Features

    Using SQL Queries

    Spark Read & Write CSV Files

    Working with JSON Files

    Working with Parquet Files

    Spark Dataset

    What Next?

    Tutorial Not Found