What you will be able to do
- Choose between a scalar UDF, a UDTF and a stored procedure for a given task
- Identify which handler languages produce UDFs that can be shared through Secure Data Sharing
- Explain what an owner's rights procedure can and cannot see, and when to declare EXECUTE AS CALLER
- Describe how a Snowflake Scripting exception handler is scoped
- Run queries concurrently inside a procedure with ASYNC and AWAIT
Key concept
Function versus procedure — A UDF or UDTF is an expression. You call it inside a query, and it always hands back a value or a set of rows. A stored procedure is a program. You run it with CALL, and it can carry out several database operations in sequence, using branching and loops.
1.Scalar UDFs and UDTFs: one row in, how much out?
Snowflake's built-in functions don't cover everything. When you need logic they lack, or a calculation your organization defines in its own standard way, you write a user-defined function. You call a UDF just as you would call a built-in function, and once it exists you can reuse it in any query. The function's logic is called its *handler*, and you write it in one of the supported languages.
The first thing to decide is the shape of the output. A plain UDF is a scalar function: each input row produces exactly one value. A user-defined table function (UDTF) produces a whole table for each input row, so it fits jobs like exploding one record into many. The documentation lists further variations. A user-defined aggregate function (UDAF) works across many rows. Vectorized UDFs and UDTFs receive their input in batches as Pandas DataFrames.
| Variation | What it returns |
|---|---|
| User-defined function (UDF) | One output row per input row, holding a single column/value (scalar) |
| User-defined table function (UDTF) | A tabular value for each input row |
| User-defined aggregate function (UDAF) | A result computed across values from multiple rows |
| Vectorized UDF / UDTF | Receives batches of rows as Pandas DataFrames |
CREATE OR REPLACE FUNCTION addone(i INT)
RETURNS INT
LANGUAGE PYTHON
RUNTIME_VERSION = '3.12'
HANDLER = 'addone_py'
AS $$
def addone_py(i):
return i+1
$$;You call it with SELECT addone(3);. The choice of shape has practical consequences too. When functions read staged files, UDTFs can process several files in parallel, but scalar UDFs currently handle files one after another. There is also a scoping detail to remember: if the handler calls CURRENT_DATABASE or CURRENT_SCHEMA, it gets back the database or schema that contains the UDF, not the one the session is currently using.
Checkpoint 1 of 8· Check yourself
An analyst needs a function that takes one address string and returns several rows, one per address component. Which variation fits?
A UDTF returns a tabular value for each input row. A scalar UDF returns only a single value per row, and a UDAF collapses many rows into one result.
“Returns a tabular value for each input row.”Source: docs.snowflake.com
Sources1
2.Handler languages, handler location and sharing
You can write a handler in Java, JavaScript, Python, Scala or SQL, but the languages don't all support the same features. Python is the only language that supports every variation (UDF, UDAF, UDTF and both vectorized forms). Scala supports only scalar UDFs when you create them with SQL. Two other properties also depend on the language. The first is where the handler code can live: in-line in the CREATE FUNCTION statement, or staged as a file. The second is whether the finished UDF can be shared, meaning used with Snowflake Secure Data Sharing.
| Language | Handler location | Sharable |
|---|---|---|
| Java | In-line or staged | No |
| JavaScript | In-line | Yes |
| Python | In-line or staged | No |
| Scala | In-line or staged | No |
| SQL | In-line | Yes |
Checkpoint 2 of 8· Check yourself
A provider wants to include a UDF in a Secure Data Sharing share. Which handler languages produce a sharable UDF?
According to the overview table, only SQL and JavaScript handlers (both in-line only) produce sharable UDFs. Java, Python and Scala UDFs are marked as not sharable.
“A sharable UDF can be used with the Snowflake Secure Data Sharing feature.”Source: docs.snowflake.com
Sources1
3.Stored procedures: procedural code you CALL
A UDF calculates a value. A stored procedure, by contrast, can automate work that takes several database operations, and it can build and run SQL statements dynamically. The documentation's example is a cleanup job that deletes old data from several tables using one cut-off date parameter. Users run one procedure instead of remembering every table name.
The workflow is: write the handler (Java, JavaScript, Python, Scala or Snowflake Scripting), create the procedure with CREATE PROCEDURE, then run it with CALL. A procedure can return a single value or, where the handler language supports it, tabular data. If you only need a procedure once, you can create a temporary procedure that lasts for the current session, or an anonymous procedure that runs and is dropped in a single statement. Neither of these requires the CREATE PROCEDURE privilege.
CREATE OR REPLACE PROCEDURE myproc(from_table STRING, to_table STRING, count INT)
RETURNS STRING
LANGUAGE PYTHON
RUNTIME_VERSION = '3.12'
PACKAGES = ('snowflake-snowpark-python')
HANDLER = 'run'
as
$$
def run(session, from_table, to_table, count):
session.table(from_table).limit(count).write.save_as_table(to_table)
return "SUCCESS"
$$;Checkpoint 3 of 8· Check yourself
A nightly job must run a sequence of statements: build a result, persist it, then log the run to an audit table. Which object is designed for this?
Stored procedures exist to automate multi-step database work with procedural control flow. Functions return a value from an expression.
“Automate tasks that require multiple database operations performed frequently.”Source: docs.snowflake.com
Sources2
4.Owner's rights versus caller's rights
A procedure can run with the privileges of the role that owns it. That lets the owner delegate a specific power, such as the cleanup job above, to users who couldn't otherwise perform it. This is also the default: when you don't specify EXECUTE AS, the procedure runs as an owner's rights procedure, because Snowflake chooses the setting that gives it less access to the caller's environment.
An owner's rights procedure follows these rules within a session:
- It runs with the owner's privileges, not the caller's. - It inherits the caller's current warehouse. - It uses the database and schema it was created in, not the ones the caller has active. - It cannot view, set or unset the caller's session variables, and it can use only a subset of the caller's session parameters.
The last rule explains a common symptom: a procedure that reads $region from the caller's session always finds it empty. To give the procedure the caller's privileges and environment, declare it EXECUTE AS CALLER. You can also switch an existing procedure with ALTER PROCEDURE … EXECUTE AS CALLER. A third option, EXECUTE AS RESTRICTED CALLER, runs with caller's rights but might not have all of the caller's privileges.
Checkpoint 4 of 8· Check yourself
A procedure created with default settings is called by an analyst whose session has a warehouse, a current schema and a session variable set. Which of these does the procedure use?
The default is owner's rights. Such a procedure inherits the caller's warehouse, but it uses its own database and schema, runs with the owner's privileges, and cannot see the caller's session variables.
“Inherit the current warehouse of the caller.”Source: docs.snowflake.com
Checkpoint 5 of 8· Exam question
A finance analyst needs one reusable tax calculation that BI dashboards can call inside SELECT lists and WHERE clauses across many queries. The logic is a single arithmetic expression with no side effects. Which object fits best?
Correct answer: C — A SQL scalar UDF created with `CREATE FUNCTION` that returns a NUMBER from its input arguments
- A. A materialized view precomputes query results from one table, not an arbitrary calculation, and cannot take parameters the way a function does.
- B. Stored procedures run through a standalone CALL statement and cannot be embedded in a SELECT list or WHERE clause, so dashboards cannot apply one row by row.
- C. A scalar UDF returns one value per input row and can be invoked anywhere an expression is allowed, including SELECT lists and WHERE clauses. A SQL body keeps the logic simple and lets the optimizer inline it.
- D. A table function is queried in the FROM clause through TABLE(), so it cannot be used as a scalar expression in a SELECT list or filter without extra joins.
5.Handling exceptions in Snowflake Scripting
A Snowflake Scripting procedure handles errors in an EXCEPTION section at the end of a block. Each block can have only one exception handler. That handler can still catch several kinds of exception, because it can contain several WHEN clauses, each naming a declared exception, plus a final WHEN OTHER THEN clause that catches anything not already listed. Each clause can be marked EXIT or CONTINUE.
The handler's scope is precise. It covers only the statements between BEGIN and EXCEPTION in the block where it is declared. It does not cover the DECLARE section. Inside the handler you can run SQL statements (including CALL), control-flow statements or nested blocks, so writing the error to an audit table is just an INSERT inside a WHEN clause. If the procedure is meant to return a value, it should return one on every exit path, including each EXIT-type WHEN clause. The EXCEPTION reference points to RAISE for raising exceptions. These sources do not describe how RAISE behaves, so this lesson doesn't either.
Checkpoint 6 of 8· Check yourself
A block's DECLARE section assigns a variable using an expression that fails. Will that block's own EXCEPTION handler catch the error?
An exception handler applies to the statements between BEGIN and EXCEPTION in its block. It does not apply to the DECLARE section.
“An exception handler applies to statements between the BEGIN and EXCEPTION sections of the block in which it is declared.”Source: docs.snowflake.com
Sources5
6.Asynchronous child jobs in procedures
By default, a Snowflake Scripting block runs its child jobs one after another, and each waits for the previous one to finish. If you put the ASYNC keyword before a query, that query runs in the background while the block moves on. The query can be a SELECT or a DML statement such as INSERT or UPDATE. Independent statements can then run concurrently, which can shorten the total run time.
There are two ways to use ASYNC. You can attach it to a query whose result goes into a RESULTSET, then run AWAIT res1; before reading that result. You can also apply it to a standalone statement and finish with AWAIT ALL;, which waits for every running child job. CANCEL stops a child job that is running for a RESULTSET, and SYSTEM$GET_RESULTSET_STATUS reports its status. Up to 4,000 child jobs can currently run concurrently. Because they can finish in any order, using LAST_QUERY_ID with them is non-deterministic.
CREATE OR REPLACE PROCEDURE test_async_child_job_inserts()
RETURNS VARCHAR
LANGUAGE SQL
AS
BEGIN
CREATE OR REPLACE TABLE test_child_job_queries1 (col1 INT);
ASYNC (INSERT INTO test_child_job_queries1(col1) VALUES(1));
ASYNC (INSERT INTO test_child_job_queries1(col1) VALUES(2));
ASYNC (INSERT INTO test_child_job_queries1(col1) VALUES(3));
AWAIT ALL;
END;Checkpoint 7 of 8· Fill the gap
Which keyword makes this procedure wait for its result-set queries before it opens cursors on them?
BEGIN
? res1;
LET cur1 CURSOR FOR res1;
OPEN cur1;You can't read a RESULTSET's results until AWAIT has run for it. CANCEL would stop the job, and ASYNC is what started it in the first place.
Source: docs.snowflake.comCheckpoint 8 of 8· Exam question
A table `customers` has a column `tags` holding comma-separated text. An analyst wrote a UDTF `split_tags(STRING)` returning one row per tag. Select TWO queries that correctly return one output row per customer-tag pair.(Select 2)
Correct answers: A, E — `SELECT c.id, t.tag FROM customers c CROSS JOIN TABLE(split_tags(c.tags)) t`; `SELECT c.id, t.tag FROM customers c, TABLE(split_tags(c.tags)) t`
- A. A cross join to TABLE(function(column)) is also a correlated lateral call, so each customer row is expanded to its tags.
- B. A table function cannot be used as a value list in a WHERE predicate, and the column `tag` is not defined at that point.
- C. A UDTF cannot be called like a scalar function in the SELECT list; it must be queried through TABLE() in the FROM clause.
- D. TABLE() belongs in the FROM clause; placing it in the SELECT list is a syntax error.
- E. Listing the table function with TABLE() after a comma in FROM performs an implicit lateral join, so the UDTF runs once for each customer row.
- F. CALL is for stored procedures, and it cannot reference columns of another table, so it cannot invoke a UDTF per row.
Sources6
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.A procedure created with default settings can read the calling user's session variables.Why is that wrong?
The default is owner's rights, and an owner's rights procedure cannot see the caller's session variables. Declare EXECUTE AS CALLER when the procedure needs the caller's environment.
Covered in Owner's rights versus caller's rights
2.Any UDF can go into a Secure Data Sharing share, whatever its handler language.Why is that wrong?
Only SQL and JavaScript UDFs are sharable. Java, Python and Scala UDFs are not.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“Also known as a scalar function, returns one output row for each input row.”
↩︎ Scalar UDFs and UDTFs: one row in, how much out?“UDTFs can process multiple files in parallel; however, UDFs currently process files serially.”
↩︎ Scalar UDFs and UDTFs: one row in, how much out?“Not all languages support referring to the handler on a stage (the handler code must instead be in-line).”
↩︎ Handler languages, handler location and sharing“A function always returns a value explicitly by specifying an expression, so it’s a good choice for calculating and return a value.”
↩︎ Key concept“A sharable UDF can be used with the Snowflake Secure Data Sharing feature.”
↩︎ Exam trap 2“Returns a tabular value for each input row.”
↩︎ Checkpoint“A sharable UDF can be used with the Snowflake Secure Data Sharing feature.”
↩︎ Checkpoint - 2.https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-overviewOfficial docs
“With a procedure, you can use branching, looping, and other programmatic constructs.”
↩︎ Stored procedures: procedural code you CALL“Create an anonymous procedure that you call immediately, and which is dropped immediately.”
↩︎ Stored procedures: procedural code you CALL“Execute code with the privileges of the role that owns the procedure”
↩︎ Owner's rights versus caller's rights“Automate tasks that require multiple database operations performed frequently.”
↩︎ Checkpoint - 3.
“the procedure runs as an owner’s rights stored procedure”
↩︎ Owner's rights versus caller's rights“Default: EXECUTE AS OWNER”
↩︎ Prediction - 4.https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-rightsOfficial docs
“Run with the privileges of the owner, not the privileges of the caller.”
↩︎ Owner's rights versus caller's rights“Cannot view, set, or unset the caller’s session variables.”
↩︎ Exam trap 1“Inherit the current warehouse of the caller.”
↩︎ Checkpoint - 5.
“Snowflake supports no more than one exception handler per block.”
↩︎ Handling exceptions in Snowflake Scripting“The WHEN OTHER [ { EXIT | CONTINUE } ] THEN clause catches any exception not yet specified.”
↩︎ Handling exceptions in Snowflake Scripting“An exception handler applies to statements between the BEGIN and EXCEPTION sections of the block in which it is declared.”
↩︎ Checkpoint - 6.https://docs.snowflake.com/en/developer-guide/snowflake-scripting/asynchronous-child-jobsOfficial docs
“To run a query as an asynchronous child job, place the ASYNC keyword before the query.”
↩︎ Asynchronous child jobs in procedures“Currently, up to 4,000 asynchronous child jobs can run concurrently.”
↩︎ Asynchronous child jobs in procedures