CertSafari
    Snowflake SnowPro Advanced: Data Engineer (DEA-C02)· Lessons

    Domain 5 · Lesson 16/22

    UDTFs, UDAFs and Snowpark Python UDFs in Snowflake

    Define User-Defined Functions (UDFs) and outline how to use them.

    10 min read
    3.57% of exam
    3 sources
    Published 5 Oct 2026
    Docs as of 4 Oct 2026

    What you will be able to do

    • Write a SQL UDTF with RETURNS TABLE, and call it in a FROM clause with the TABLE keyword, including in joins
    • Implement the five handler methods a Python UDAF needs, and register the UDAF with Snowpark
    • Register anonymous, named, temporary and permanent UDFs with the Snowpark Python API, and manage their dependencies

    1.User-defined table functions (UDTFs) in SQL

    A user-defined table function (UDTF) returns a set of rows: zero, one or many, each with one or more columns. You declare the output shape with RETURNS TABLE(...), listing name-and-type pairs. At least one output column is required. In a SQL UDTF, the body must be a SELECT expression, and its columns must match the declared types position by position. A UDTF can take at most 500 input arguments and return at most 500 output columns.

    A SQL UDTF that returns matching orders, and how to call itsql
    create or replace function orders_for_product(PROD_ID varchar)
        returns table (Product_ID varchar, Quantity_Sold numeric(11, 2))
        as
        $$
            select product_ID, quantity_sold
                from orders
                where product_ID = PROD_ID
        $$
        ;
    
    select product_id, quantity_sold
        from table(orders_for_product('compostable bags'))
        order by product_id;

    You don't call a UDTF in the SELECT list as you would a scalar function. You call it in the FROM clause, with its name and arguments inside TABLE( ... ). Its output columns can then be used anywhere a normal table column can.

    UDTFs can also take part in joins. A column from the other table can be passed in as the argument, so the function runs once for each row. In the example below, each year from favorite_years produces that year's favorite colors. If you need extra join conditions, put them in a WHERE clause, not in ON.

    Joining a table to a UDTF, passing the join column as the argumentsql
    select *
        from favorite_years y join table(favorite_colors(y.year)) c
        order by year, color;

    Checkpoint 1 of 6· Fill the gap

    Which keyword must wrap the UDTF call in the FROM clause?

    select product_id, quantity_sold
        from  ? (orders_for_product('compostable bags'))
        order by product_id;

    Checkpoint 2 of 6· Exam question

    A team is migrating logic from an older Snowflake project that used JavaScript UDFs for string parsing and now wants to add unit-testable, module-based Python code with third-party libraries such as `regex`, while keeping the function callable from plain SQL. Which change best meets the new requirement?

    Sources1

    2.User-defined aggregate functions (UDAFs) in Python

    A user-defined aggregate function (UDAF) takes one or more rows as input and produces a single row of output. Creating one takes two steps. First, write a handler class. Then register it, either with the Snowpark API or with SQL's CREATE FUNCTION. The same class works for both.

    Snowflake calls the handler's methods at runtime. All five are required.

    Required methods of a UDAF handler class
    MethodWhat it does
    __init__Initializes the internal state of an aggregate
    aggregate_stateReturns the internal state; must have a @property decorator; the state must be serializable by the Python pickle library
    accumulateAccumulates the state of the aggregate based on the new input row
    mergeCombines two intermediate aggregated states
    finishProduces the final result based on the aggregated state
    A sum_int UDAF handler, registered with the Snowpark udaf functionpython
      class PythonSumUDAF:
        def __init__(self):
          # This aggregate state is a primitive Python data type.
          self._partial_sum = 0
    
        @property
        def aggregate_state(self):
          return self._partial_sum
    
        def accumulate(self, input_value):
          self._partial_sum += input_value
    
        def merge(self, other_partial_sum):
          self._partial_sum += other_partial_sum
    
        def finish(self):
          return self._partial_sum
      sum_udaf = udaf(PythonSumUDAF, name="sum_int", replace=True, return_type=IntegerType(), input_types=[IntegerType()])

    Like a scalar UDF, a UDAF can be registered as a named or an anonymous function. There are three ways to register it:

    - the udaf function, used directly or as a @udaf decorator on the class - UDAFRegistration.register - register_from_file, which points to a Python file or zip file

    After registration, you can call the UDAF from SQL or from a DataFrame, for example df.agg(sum_udaf("a")).

    Checkpoint 3 of 6· Match them up

    Match each UDAF handler method to its role

    Tap a term, then the definition that fits it.

    Sources2

    3.Snowpark UDFs in Python: anonymous, named, temporary and permanent

    With the Snowpark API, you can turn a Python lambda or function into a UDF and use it on DataFrames. Snowpark uploads your function's code to an internal stage. When you call the UDF, it runs on the server where the data is. A UDF created with SQL's CREATE FUNCTION can also be called from Snowpark.

    There are two kinds of registration:

    - An anonymous UDF is assigned to a variable. You can use it for as long as that variable is in scope. - A named UDF is registered with a name argument. Use this when you need to call it by name, or reuse it in a later session.

    Calling register or udf creates a temporary UDF by default, which lasts only for the current session. To make it permanent, set is_permanent=True and give a stage_location where the code and its dependencies are uploaded. In code that may run in several sessions, use the register method rather than udf, so that the default Session object can always be found.

    Checkpoint 4 of 6· Fill the gap

    Which argument makes this named UDF survive beyond the current session?

    @udf(name="minus_one",  ? =True, stage_location="@my_stage", replace=True)
    def minus_one(x: int) -> int:
      return x-1

    Dependencies have to be declared:

    - Your own modules, zip files, resource files or directories are added with session.add_import(). Snowpark uploads them to an internal stage and imports them when the UDF runs. The Snowpark library itself is not uploaded automatically. Python's built-in libraries are already available on the server. - Third-party packages come from the Snowflake Anaconda channel. Add them for the whole session with session.add_packages or session.add_requirements, or for one UDF with @udf(packages=[...]). UDF-level packages override session-level ones.

    Pinning versions matters for production. A permanent UDF resolves its dependencies once, when it is registered. A temporary UDF should list exact versions, or a newer package version may be picked up later.

    Checkpoint 5 of 6· Check yourself

    In a local development environment, how do you make your own my_module.py available to a Snowpark UDF?

    Checkpoint 6 of 6· Exam question

    An engineering team already has a scoring model packaged as a compiled JAR that exposes a `MyScorer.score(String)` method, and they need to invoke that exact compiled artifact from Snowflake SQL without rewriting the model logic in another language. Which UDF definition matches this need?

    Sources3

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 1.A UDTF is called in the SELECT list like a scalar function, and extra join conditions go in an ON clause.Why is that wrong?

      A UDTF is called in the FROM clause, inside TABLE( ... ). When it is joined to another table, extra conditions go in a WHERE clause, not ON.

      Covered in User-defined table functions (UDTFs) in SQL

    2. 2.Giving a Snowpark UDF a name makes it permanent.Why is that wrong?

      Named and anonymous UDFs are both temporary by default. A permanent UDF needs is_permanent=True and a stage_location.

      Covered in Snowpark UDFs in Python: anonymous, named, temporary and permanent

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 1.
      “return a set of rows, consisting of 0, 1, or multiple rows, each of which has 1 or more columns.”
      ↩︎ User-defined table functions (UDTFs) in SQL
      “of a SQL UDTF must be a SELECT expression.”
      ↩︎ User-defined table functions (UDTFs) in SQL
      “Tabular functions (UDTFs) have a limit of 500 input arguments and 500 output columns.”
      ↩︎ User-defined table functions (UDTFs) in SQL
      “you must include the UDTF name and arguments inside parentheses following the TABLE keyword.”
      ↩︎ User-defined table functions (UDTFs) in SQL
      “note that the join column from the table is passed as an argument to the function.”
      ↩︎ User-defined table functions (UDTFs) in SQL
      “note that a WHERE clause, rather than ON, must be used for additional join conditions”
      ↩︎ Exam trap 1
    2. 2.
      “A UDAF takes one or more rows as input and produces a single row of output.”
      ↩︎ User-defined aggregate functions (UDAFs) in Python
      “You can also use udaf as a @udaf decorator on the handler class.”
      ↩︎ User-defined aggregate functions (UDAFs) in Python
      “Use the register_from_file function, pointing to a Python file or zip file containing Python source code.”
      ↩︎ User-defined aggregate functions (UDAFs) in Python
      “Combines two intermediate aggregated states.”
      ↩︎ Checkpoint
    3. 3.
      “When you call the UDF, the Snowpark library executes your function on the server, where the data is.”
      ↩︎ Snowpark UDFs in Python: anonymous, named, temporary and permanent
      “use the register method to register UDFs, rather than using the udf function.”
      ↩︎ Snowpark UDFs in Python: anonymous, named, temporary and permanent
      “You can add the UDF-level packages to overwrite the session-level packages you might have added previously.”
      ↩︎ Snowpark UDFs in Python: anonymous, named, temporary and permanent
      “When you create a permanent UDF, the UDF is created and registered only once.”
      ↩︎ Snowpark UDFs in Python: anonymous, named, temporary and permanent
      “you must also set the stage_location argument to the stage location where the Python file for the UDF and its dependencies are uploaded.”
      ↩︎ Exam trap 2
      “Calling register or udf will create a temporary UDF that you can use in the current session.”
      ↩︎ Prediction
      “To do this using your local development environment, you must call Session.add_import() in your code.”
      ↩︎ Checkpoint

    Spotted a mistake, or was something unclear? Tell us.