Writing stored procedures in Snowflake Scripting

This topic provides an introduction to writing a stored procedure in SQL by using Snowflake Scripting. For more information about Snowflake Scripting, see the Snowflake Scripting Developer Guide.

Introduction

To write a stored procedure that uses Snowflake Scripting:

Snowflake limits the maximum size of the source code in the body of a Snowflake Scripting stored procedure. Snowflake recommends limiting the size to 100 KB. (The code is stored in a compressed form, and the exact limit depends on the compressibility of the code.)

You can capture log and trace data as your handler code executes. For more information, see Logging, tracing, and metrics.

Note

The following is an example of a simple stored procedure that returns the value of the argument that is passed in:

CREATE OR REPLACE PROCEDURE output_message(message VARCHAR)
RETURNS VARCHAR NOT NULL
LANGUAGE SQL
AS
BEGIN
  RETURN message;
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, the Classic Console, and Python Connector):

CREATE OR REPLACE PROCEDURE output_message(message VARCHAR)
RETURNS VARCHAR NOT NULL
LANGUAGE SQL
AS
$$
BEGIN
  RETURN message;
END;
$$
;
Copy

The following is an example of calling the stored procedure:

CALL output_message('Hello World');
Copy

The following is an example of creating and calling an anonymous stored procedure by using the WITH … CALL … command:

WITH anonymous_output_message AS PROCEDURE (message VARCHAR)
  RETURNS VARCHAR NOT NULL
  LANGUAGE SQL
  AS
  $$
  BEGIN
    RETURN message;
  END;
  $$
CALL anonymous_output_message('Hello World');
Copy

Note that in an anonymous stored procedure, you must use string literal delimiters (' or $$) around the body of the procedure.

Using arguments passed to a stored procedure

If you pass in any arguments to your stored procedure, you can refer to those arguments by name in any Snowflake Scripting expression. See the next sections for more details:

Simple example of using arguments passed to a stored procedure

The following stored procedure uses the values of the arguments in IF and RETURN statements.

CREATE OR REPLACE PROCEDURE return_greater(number_1 INTEGER, number_2 INTEGER)
RETURNS INTEGER NOT NULL
LANGUAGE SQL
AS
BEGIN
  IF (number_1 > number_2) THEN
    RETURN number_1;
  ELSE
    RETURN number_2;
  END IF;
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, the Classic Console, and Python Connector):

CREATE OR REPLACE PROCEDURE return_greater(number_1 INTEGER, number_2 INTEGER)
RETURNS INTEGER NOT NULL
LANGUAGE SQL
AS
$$
BEGIN
  IF (number_1 > number_2) THEN
    RETURN number_1;
  ELSE
    RETURN number_2;
  END IF;
END;
$$
;
Copy

The following is an example of calling the stored procedure:

CALL return_greater(2, 3);
Copy

Using an argument in a SQL statement (binding)

As is the case with Snowflake Scripting variables, if you need to use an argument in a SQL statement, put a colon (:) in front of the argument name. For more information, see Using a variable in a SQL statement (binding).

The following sections contain examples that use bind variables in stored procedures:

Example that uses a bind variable in a WHERE clause

The following stored procedure uses the id argument in the WHERE clause of a SELECT statement. In the WHERE clause, the argument is specified as :id.

CREATE OR REPLACE PROCEDURE find_invoice_by_id(id VARCHAR)
RETURNS TABLE (id INTEGER, price NUMBER(12,2))
LANGUAGE SQL
AS
DECLARE
  res RESULTSET DEFAULT (SELECT * FROM invoices WHERE id = :id);
BEGIN
  RETURN TABLE(res);
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, the Classic Console, and Python Connector):

CREATE OR REPLACE PROCEDURE find_invoice_by_id(id VARCHAR)
RETURNS TABLE (id INTEGER, price NUMBER(12,2))
LANGUAGE SQL
AS
$$
DECLARE
  res RESULTSET DEFAULT (SELECT * FROM invoices WHERE id = :id);
BEGIN
  RETURN TABLE(res);
END;
$$
;
Copy

The following is an example of calling the stored procedure:

CALL find_invoice_by_id('2');
Copy

In addition, the TO_QUERY function provides a simple syntax for accepting a SQL string directly in the FROM clause of a SELECT statement. For a comparison of the TO_QUERY function with dynamic SQL, see Constructing SQL at runtime.

Example of using a bind variable to set the value of a property

The following stored procedure uses the comment argument to add a comment for a table in a CREATE TABLE statement. In the statement, the argument is specified as :comment.

CREATE OR REPLACE PROCEDURE test_bind_comment(comment VARCHAR)
RETURNS STRING
LANGUAGE SQL
AS
BEGIN
  CREATE OR REPLACE TABLE test_table_with_comment(a VARCHAR, n NUMBER) COMMENT = :comment;
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, the Classic Console, and Python Connector):

CREATE OR REPLACE PROCEDURE test_bind_comment(comment VARCHAR)
RETURNS STRING
LANGUAGE SQL
AS
$$
BEGIN
  CREATE OR REPLACE TABLE test_table_with_comment(a VARCHAR, n NUMBER) COMMENT = :comment;
END;
$$
;
Copy

The following is an example of calling the stored procedure:

CALL test_bind_comment('My Test Table');
Copy

View the comment for the table by querying the TABLES view in the INFORMATION_SCHEMA:

SELECT comment FROM information_schema.tables WHERE table_name='TEST_TABLE_WITH_COMMENT';
Copy
+---------------+
| COMMENT       |
|---------------|
| My Test Table |
+---------------+

You can also view the comment by running a SHOW TABLES command.

Example that uses bind variables to set parameters in a command

Assume you have a stage named st with CSV files:

CREATE OR REPLACE STAGE st;
PUT file://good_data.csv @st;
PUT file://errors_data.csv @st;
Copy

You want to load the data in the CSV files into a table named test_bind_stage_and_load:

CREATE OR REPLACE TABLE test_bind_stage_and_load (a VARCHAR, b VARCHAR, c VARCHAR);
Copy

The following stored procedure uses the FROM, ON_ERROR, and VALIDATION_MODE parameters in a COPY INTO <table> statement. In the statement, the parameter values are specified as :my_stage_name, :on_error, and :valid_mode, respectively.

CREATE OR REPLACE PROCEDURE test_copy_files_validate(
  my_stage_name VARCHAR,
  on_error VARCHAR,
  valid_mode VARCHAR)
RETURNS STRING
LANGUAGE SQL
AS
BEGIN
  COPY INTO test_bind_stage_and_load
    FROM :my_stage_name
    ON_ERROR=:on_error
    FILE_FORMAT=(type='csv')
    VALIDATION_MODE=:valid_mode;
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, the Classic Console, and Python Connector):

CREATE OR REPLACE PROCEDURE test_copy_files_validate(
  my_stage_name VARCHAR,
  on_error VARCHAR,
  valid_mode VARCHAR)
RETURNS STRING
LANGUAGE SQL
AS
$$
BEGIN
  COPY INTO test_bind_stage_and_load
    FROM :my_stage_name
    ON_ERROR=:on_error
    FILE_FORMAT=(type='csv')
    VALIDATION_MODE=:valid_mode;
END;
$$
;
Copy

The following is an example of calling the stored procedure:

CALL test_copy_files_validate('@st', 'skip_file', 'return_all_errors');
Copy

Examples that use a bind variable for an array

You can expand a bind variable that represents an array into a list of individual values by using the spread operator (**). For more information and examples, see Expansion operators.

Using an argument as an object identifier

If you need to use an argument to refer to an object (for example, a table name in the FROM clause of a SELECT statement), use the IDENTIFIER keyword to indicate that the argument represents an object identifier. For example:

CREATE OR REPLACE PROCEDURE get_row_count(table_name VARCHAR)
RETURNS INTEGER NOT NULL
LANGUAGE SQL
AS
DECLARE
  row_count INTEGER DEFAULT 0;
  res RESULTSET DEFAULT (SELECT COUNT(*) AS COUNT FROM IDENTIFIER(:table_name));
  c1 CURSOR FOR res;
BEGIN
  FOR row_variable IN c1 DO
    row_count := row_variable.count;
  END FOR;
  RETURN row_count;
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, the Classic Console, and Python Connector):

CREATE OR REPLACE PROCEDURE get_row_count(table_name VARCHAR)
RETURNS INTEGER NOT NULL
LANGUAGE SQL
AS
$$
DECLARE
  row_count INTEGER DEFAULT 0;
  res RESULTSET DEFAULT (SELECT COUNT(*) AS COUNT FROM IDENTIFIER(:table_name));
  c1 CURSOR FOR res;
BEGIN
  FOR row_variable IN c1 DO
    row_count := row_variable.count;
  END FOR;
  RETURN row_count;
END;
$$
;
Copy

The following is an example of calling the stored procedure:

CALL get_row_count('invoices');
Copy

The following example executes a CREATE TABLE … AS SELECT (CTAS) statement in a stored procedure based on the table names provided in arguments.

CREATE OR REPLACE PROCEDURE ctas_sp(existing_table VARCHAR, new_table VARCHAR)
  RETURNS TEXT
  LANGUAGE SQL
AS
BEGIN
  CREATE OR REPLACE TABLE IDENTIFIER(:new_table) AS
    SELECT * FROM IDENTIFIER(:existing_table);
  RETURN 'Table created';
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, the Classic Console, and Python Connector):

CREATE OR REPLACE PROCEDURE ctas_sp(existing_table VARCHAR, new_table VARCHAR)
  RETURNS TEXT
  LANGUAGE SQL
AS
$$
BEGIN
  CREATE OR REPLACE TABLE IDENTIFIER(:new_table) AS
    SELECT * FROM IDENTIFIER(:existing_table);
  RETURN 'Table created';
END;
$$
;
Copy

Before calling the procedure, create a simple table and insert data:

CREATE OR REPLACE TABLE test_table_for_ctas_sp (
  id NUMBER(2),
  v  VARCHAR(2))
AS SELECT
  column1,
  column2,
FROM
  VALUES
    (1, 'a'),
    (2, 'b'),
    (3, 'c');
Copy

Call the stored procedure to create a new table that is based on this table:

CALL ctas_sp('test_table_for_ctas_sp', 'test_table_for_ctas_sp_backup');
Copy

Using an argument when building a string for a SQL statement

Note that if you are building a SQL statement as a string to be passed to EXECUTE IMMEDIATE (see Assigning a query to a declared RESULTSET), do not prefix the argument with a colon. For example:

CREATE OR REPLACE PROCEDURE find_invoice_by_id_via_execute_immediate(id VARCHAR)
RETURNS TABLE (id INTEGER, price NUMBER(12,2))
LANGUAGE SQL
AS
DECLARE
  select_statement VARCHAR;
  res RESULTSET;
BEGIN
  select_statement := 'SELECT * FROM invoices WHERE id = ' || id;
  res := (EXECUTE IMMEDIATE :select_statement);
  RETURN TABLE(res);
END;
Copy

Returning tabular data

If you need to return tabular data (for example, data from a RESULTSET) from your stored procedure, specify RETURNS TABLE(…) in your CREATE PROCEDURE statement.

If you know the Snowflake data types of the columns in the returned table, specify the column names and types in the RETURNS TABLE().

CREATE OR REPLACE PROCEDURE get_top_sales()
RETURNS TABLE (sales_date DATE, quantity NUMBER)
...
Copy

Otherwise (for example, if you are determining the column types during run time), you can omit the column names and types:

CREATE OR REPLACE PROCEDURE get_top_sales()
RETURNS TABLE ()
...
Copy

Note

Currently, in the RETURNS TABLE(...) clause, you can’t specify GEOGRAPHY as a column type. This applies whether you are creating a stored or anonymous procedure.

CREATE OR REPLACE PROCEDURE test_return_geography_table_1()
  RETURNS TABLE(g GEOGRAPHY)
  ...
Copy
WITH test_return_geography_table_1() AS PROCEDURE
  RETURNS TABLE(g GEOGRAPHY)
  ...
CALL test_return_geography_table_1();
Copy

If you attempt to specify GEOGRAPHY as a column type, calling the stored procedure results in the error:

Stored procedure execution error: data type of returned table does not match expected returned table type
Copy

To work around this issue, you can omit the column arguments and types in RETURNS TABLE().

CREATE OR REPLACE PROCEDURE test_return_geography_table_1()
  RETURNS TABLE()
  ...
Copy
WITH test_return_geography_table_1() AS PROCEDURE
  RETURNS TABLE()
  ...
CALL test_return_geography_table_1();
Copy

If you need to return the data in a RESULTSET, use TABLE() in your RETURN statement.

For example:

CREATE OR REPLACE PROCEDURE get_top_sales()
RETURNS TABLE (sales_date DATE, quantity NUMBER)
LANGUAGE SQL
AS
DECLARE
  res RESULTSET DEFAULT (SELECT sales_date, quantity FROM sales ORDER BY quantity DESC LIMIT 10);
BEGIN
  RETURN TABLE(res);
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, the Classic Console, and Python Connector):

CREATE OR REPLACE PROCEDURE get_top_sales()
RETURNS TABLE (sales_date DATE, quantity NUMBER)
LANGUAGE SQL
AS
$$
DECLARE
  res RESULTSET DEFAULT (SELECT sales_date, quantity FROM sales ORDER BY quantity DESC LIMIT 10);
BEGIN
  RETURN TABLE(res);
END;
$$
;
Copy

The following is an example of calling the stored procedure:

CALL get_top_sales();
Copy

Calling a stored procedure from another stored procedure

In a stored procedure, if you need to call another stored procedure, use one of the following approaches:

Calling a stored procedure without using the returned value

Use a CALL statement to call the stored procedure (as you normally would).

If you need to pass in any variables or arguments as input arguments in the CALL statement, remember to use a colon (:) in front of the variable name. (See Using a variable in a SQL statement (binding).)

The following is an example of a stored procedure that calls another stored procedure but does not depend on the return value.

First, create a table for use in the example:

-- Create a table for use in the example.
CREATE OR REPLACE TABLE int_table (value INTEGER);
Copy

Then, create the stored procedure that you will call from another stored procedure:

-- Create a stored procedure to be called from another stored procedure.
CREATE OR REPLACE PROCEDURE insert_value(value INTEGER)
RETURNS VARCHAR NOT NULL
LANGUAGE SQL
AS
BEGIN
  INSERT INTO int_table VALUES (:value);
  RETURN 'Rows inserted: ' || SQLROWCOUNT;
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, the Classic Console, and Python Connector):

-- Create a stored procedure to be called from another stored procedure.
CREATE OR REPLACE PROCEDURE insert_value(value INTEGER)
RETURNS VARCHAR NOT NULL
LANGUAGE SQL
AS
$$
BEGIN
  INSERT INTO int_table VALUES (:value);
  RETURN 'Rows inserted: ' || SQLROWCOUNT;
END;
$$
;
Copy

Next, create a second stored procedure that calls the first stored procedure:

CREATE OR REPLACE PROCEDURE insert_two_values(value1 INTEGER, value2 INTEGER)
RETURNS VARCHAR NOT NULL
LANGUAGE SQL
AS
BEGIN
  CALL insert_value(:value1);
  CALL insert_value(:value2);
  RETURN 'Finished calling stored procedures';
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, the Classic Console, and Python Connector):

CREATE OR REPLACE PROCEDURE insert_two_values(value1 INTEGER, value2 INTEGER)
RETURNS VARCHAR NOT NULL
LANGUAGE SQL
AS
$$
BEGIN
  CALL insert_value(:value1);
  CALL insert_value(:value2);
  RETURN 'Finished calling stored procedures';
END;
$$
;
Copy

Finally, call the second stored procedure:

CALL insert_two_values(4, 5);
Copy

Using the value returned from a stored procedure call

If are calling a stored procedure that returns a scalar value and you need to access that value, use the INTO :snowflake_scripting_variable clause in the CALL statement to capture the value in a Snowflake Scripting variable.

The following example calls the get_row_count stored procedure that was defined in Using an argument as an object identifier.

CREATE OR REPLACE PROCEDURE count_greater_than(table_name VARCHAR, maximum_count INTEGER)
  RETURNS BOOLEAN NOT NULL
  LANGUAGE SQL
  AS
  DECLARE
    count1 NUMBER;
  BEGIN
    CALL get_row_count(:table_name) INTO :count1;
    IF (:count1 > maximum_count) THEN
      RETURN TRUE;
    ELSE
      RETURN FALSE;
    END IF;
  END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, the Classic Console, and Python Connector):

CREATE OR REPLACE PROCEDURE count_greater_than(table_name VARCHAR, maximum_count INTEGER)
  RETURNS BOOLEAN NOT NULL
  LANGUAGE SQL
  AS
  $$
  DECLARE
    count1 NUMBER;
  BEGIN
    CALL get_row_count(:table_name) INTO :count1;
    IF (:count1 > maximum_count) THEN
      RETURN TRUE;
    ELSE
      RETURN FALSE;
    END IF;
  END;
  $$
  ;
Copy

The following is an example of calling the stored procedure:

CALL count_greater_than('invoices', 3);
Copy

If the stored procedure returns a table, you can capture the return value by setting a RESULTSET to a string containing the CALL statement. (See Assigning a query to a declared RESULTSET.)

To retrieve the return value from the call, you can use a CURSOR for the RESULTSET. For example:

DECLARE
  res1 RESULTSET;
BEGIN
res1 := (CALL my_procedure());
LET c1 CURSOR FOR res1;
FOR row_variable IN c1 DO
  IF (row_variable.col1 > 0) THEN
    ...;
  ELSE
    ...;
  END IF;
END FOR;
...
Copy

Using nested stored procedures

A nested stored procedure is a stored procedure that’s defined within the scope of an anonymous block or a block in another stored procedure (the parent stored procedure).

You declare a nested stored procedure in the DECLARE section of a block, which can be part of a CREATE PROCEDURE statement. The following example shows a nested stored procedure declaration:

DECLARE
  <nested_stored_procedure_name> PROCEDURE (<arguments>)
     RETURNS <data_type>
     AS
     BEGIN
       <nested_procedure_procedure_statements>
     END;
BEGIN
  <statements>
END;
Copy

For information about the declaration syntax of a nested stored procedure, see Nested stored procedure declaration syntax.

A nested stored procedure only exists within the scope of its block. It can be called from any section of its block (DECLARE, BEGIN … END, and EXCEPTION). A single block can contain multiple nested stored procedures, and one nested stored procedure can call another nested stored procedure in the same block. A nested procedure can’t be called or accessed from outside of its block.

A nested stored procedure operates in the same security context as the block that defines it. When a nested stored procedure is defined in a parent stored procedure, it automatically runs with the same privileges as the parent stored procedure.

Note

Both a nested stored procedure declaration and the CALL WITH command create a temporary stored procedure with limited scope. They differ in the following ways:

  • A CALL WITH statement can appear anywhere that a SQL statement can, including within a stored procedure, but a nested stored procedure declaration must be in a Snowflake Scripting block.

  • A CALL WITH stored procedure only exists in the scope of its statement, but a nested stored procedure exists in the scope of its Snowflake Scripting block.

Benefits of nested stored procedures

Nested stored procedures provide the following benefits:

  • They can enhance and simplify security by encapsulating logic inside an anonymous block or parent stored procedure, which prevents access to it from outside the block or parent.

  • They keep code modular by splitting it logically into smaller chunks, which can make it easier to maintain and debug.

  • They improve maintainability by reducing the need for global variables or additional arguments, because a nested stored procedure can directly access the local variables of its block.

Usage notes for calling nested stored procedures

The following usage notes apply to calling a nested stored procedure:

Usage notes for variables in a nested stored procedure

The following usage notes apply to variables in a nested stored procedure:

  • A nested stored procedure can reference variables from its block that were declared before the nested stored procedure declaration in the DECLARE section of its block. It can’t reference variables declared after it in the DECLARE section.

  • A nested stored procedure can’t access variables declared in a LET statement in the BEGIN … END section of a block.

  • The value of a referenced variable reflects its value at the time when the nested stored procedure is called.

  • A nested stored procedure can modify a referenced variable value, and the modified value persists in the block and across multiple invocations of the same nested procedure in a single execution of its anonymous block or in a single call to its parent stored procedure.

  • The value of a variable that was declared before a nested stored procedure call can be passed as an argument to the nested stored procedure. The variable value can be passed as an argument in a call even if the variable was declared after the nested stored procedure declaration or in a LET statement.

For example, the following stored procedure declares several variables:

CREATE OR REPLACE PROCEDURE outer_sp ()
RETURNS NUMBER
LANGUAGE SQL
AS
$$
DECLARE
  var_before_nested_proc NUMBER DEFAULT 1;
  test_nested_variables PROCEDURE(arg1 NUMBER)
    -- <nested_sp_logic>
  var_after_nested_proc NUMBER DEFAULT 2;
BEGIN
  LET var_let_before_call NUMBER DEFAULT 3;
  LET result := CALL nested_proc(:<var_name>);
  LET var_let_after_call NUMBER DEFAULT 3;
  RETURN result;
END;
$$;
Copy

In this example, only var_before_nested_proc can be referenced in nested_sp_logic.

In the nested stored procedure call, the value of any of the following variables can be passed to the nested stored procedure as an argument in var_name:

  • var_before_nested_proc

  • var_after_nested_proc

  • var_let_before_call

The value of var_let_after_call can’t be passed to the nested stored procedure as an argument.

Limitations for nested stored procedures

The following limitations apply to defining nested stored procedures:

  • They can’t be defined inside other nested stored procedures or inside control structures, such as FOR or WHILE loops.

  • Each nested stored procedure must have a unique name in its block. That is, nested stored procedures can’t be overloaded.

  • They don’t support output (OUT) arguments.

  • They don’t support optional arguments with default values.

The following limitations apply to calling nested stored procedures:

  • They can’t be called in an EXECUTE IMMEDIATE statement.

  • They can’t be called in asynchronous child jobs.

  • They don’t support named input arguments (arg_name => arg). Arguments must be specified by position. For more information, see CALL.

Examples of nested stored procedures

The following examples use nested stored procedures:

Define a nested stored procedure that returns tabular data

The following example defines a nested stored procedure that returns a tabular data. The example creates a parent stored procedure called nested_procedure_example_table with a nested stored procedure called nested_return_table. The code includes the following logic:

  • Declares a variable called res of type RESULTSET.

  • Includes the following logic in the nested stored procedure:

    • Declares a variable called res2.

    • Inserts values into a table called nested_table.

    • Sets the res2 variable to the results of a SELECT on the table.

    • Returns the tabular data in the result set.

  • Creates the table nested_table in the parent stored procedure.

  • Calls the nested stored procedure nested_return_table and sets the res variable to the results of the call to the nested stored procedure.

  • Returns the tabular results in the res variable.

CREATE OR REPLACE PROCEDURE nested_procedure_example_table()
RETURNS TABLE()
LANGUAGE SQL
AS
$$
DECLARE
  res RESULTSET;
  nested_return_table PROCEDURE()
    RETURNS TABLE()
    AS
    DECLARE
      res2 RESULTSET;
    BEGIN
      INSERT INTO nested_table VALUES(1);
      INSERT INTO nested_table VALUES(2);
      res2 := (SELECT * FROM nested_table);
      RETURN TABLE(res2);
    END;
BEGIN
  CREATE OR REPLACE TABLE nested_table(col1 INT);
  res := (CALL nested_return_table());
  RETURN TABLE(res);
END;
$$;
Copy

Call the stored procedure:

CALL nested_procedure_example_table();
Copy
+------+
| COL1 |
|------|
|    1 |
|    2 |
+------+

Define a nested stored procedure that returns a scalar value

The following example defines a nested stored procedure that returns a scalar value. The example creates a parent stored procedure called nested_procedure_example_scalar with a nested stored procedure called simple_counter. The code includes the following logic:

  • Declares a variable called counter of type NUMBER, and sets the value of this variable to 0.

  • Specifies that the nested stored procedure adds 1 to the current value of the counter variable.

  • Calls the nested stored procedure three times in the parent stored procedure. The value of the counter variable is carried over between invocations of the nested stored procedure.

  • Returns the value of the counter variable, which is 3.

CREATE OR REPLACE PROCEDURE nested_procedure_example_scalar()
RETURNS VARCHAR
LANGUAGE SQL
AS
$$
DECLARE
  counter NUMBER := 0;
  simple_counter PROCEDURE()
    RETURNS VARCHAR
    AS
    BEGIN
      counter := counter + 1;
      RETURN counter;
    END;
BEGIN
  CALL simple_counter();
  CALL simple_counter();
  CALL simple_counter();
  RETURN counter;
END;
$$;
Copy

Call the stored procedure:

CALL nested_procedure_example_scalar();
Copy
+---------------------------------+
| NESTED_PROCEDURE_EXAMPLE_SCALAR |
|---------------------------------|
| 3                               |
+---------------------------------+

Define a nested stored procedure in an anonymous block

The following example is the same as the example in Define a nested stored procedure that returns a scalar value, except that it defines a nested stored procedure in an anonymous block instead of a stored procedure:

EXECUTE IMMEDIATE $$
DECLARE
  counter NUMBER := 0;
  simple_counter PROCEDURE()
    RETURNS VARCHAR
    AS
    BEGIN
      counter := counter + 1;
      RETURN counter;
    END;
BEGIN
  CALL simple_counter();
  CALL simple_counter();
  CALL simple_counter();
  RETURN counter;
END;
$$;
Copy
+-----------------+
| anonymous block |
|-----------------|
|               3 |
+-----------------+

Define a nested stored procedure that is passed arguments

The following example defines a nested stored procedure that is passed arguments. In the example, the nested stored procedure inserts values into the following table:

CREATE OR REPLACE TABLE log_nested_values(col1 INT, col2 INT);
Copy

The example creates a parent stored procedure called nested_procedure_example_arguments with a nested stored procedure called log_and_multiply_numbers. The nested stored procedure takes two arguments of type NUMBER. The code includes the following logic:

  • Declares variables a, b, and x of type NUMBER.

  • Includes a nested stored procedure that performs the following actions:

    • Inserts the two number values passed to it by the parent stored procedure into the log_nested_values table using bind variables.

    • Sets the value of variable x to the result of multiplying the two argument values.

    • Returns the value of x to the parent stored procedure.

  • Sets the value of variable a to 5 and variable b to 10.

  • Calls the nested stored procedure.

  • Returns the value of the x variable, which was set in the nested stored procedure.

CREATE OR REPLACE PROCEDURE nested_procedure_example_arguments()
RETURNS NUMBER
LANGUAGE SQL
AS
$$
DECLARE
  a NUMBER;
  b NUMBER;
  x NUMBER;
  log_and_multiply_numbers PROCEDURE(num1 NUMBER, num2 NUMBER)
    RETURNS NUMBER
    AS
    BEGIN
      INSERT INTO log_nested_values VALUES(:num1, :num2);
      x := :num1 * :num2;
      RETURN x;
    END;
BEGIN
  a := 5;
  b := 10;
  CALL log_and_multiply_numbers(:a, :b);
  RETURN x;
END;
$$;
Copy

Call the stored procedure:

CALL nested_procedure_example_arguments();
Copy
+------------------------------------+
| NESTED_PROCEDURE_EXAMPLE_ARGUMENTS |
|------------------------------------|
|                                 50 |
+------------------------------------+

Query the log_nested_values table to confirm that the nested stored procedure inserted the values passed to it:

SELECT * FROM log_nested_values;
Copy
+------+------+
| COL1 | COL2 |
|------+------|
|    5 |   10 |
+------+------+

Define a nested stored procedure that calls another nested stored procedure

The following example defines a nested stored procedure that calls another nested stored procedure. The example creates a parent stored procedure called nested_procedure_example_call_from_nested with two nested stored procedures called counter_nested_proc and call_counter_nested_proc. The code includes the following logic:

  • Declares a variable called counter of type NUMBER, and sets the value of this variable to 0.

  • Includes the nested stored procedure counter_nested_proc that adds 10 to the value of counter.

  • Includes the nested stored procedure call_counter_nested_proc that adds 15 to the value of counter and also calls counter_nested_proc (which adds another 10 to the value of counter).

  • Calls both nested stored procedures in the parent stored procedure.

  • Returns the value of the counter variable, which is 35.

CREATE OR REPLACE PROCEDURE nested_procedure_example_call_from_nested()
RETURNS NUMBER
LANGUAGE SQL
AS
$$
DECLARE
  counter NUMBER := 0;
  counter_nested_proc PROCEDURE()
    RETURNS NUMBER
    AS
    DECLARE
      var1 NUMBER := 10;
    BEGIN
      counter := counter + var1;
    END;
  call_counter_nested_proc PROCEDURE()
    RETURNS NUMBER
    AS
    DECLARE
      var2 NUMBER := 15;
    BEGIN
      counter := counter + var2;
      CALL counter_nested_proc();
    END;
BEGIN
  counter := 0;
  CALL counter_nested_proc();
  CALL call_counter_nested_proc();
  RETURN counter;
END;
$$;
Copy

Call the stored procedure:

CALL nested_procedure_example_call_from_nested();
Copy
+-------------------------------------------+
| NESTED_PROCEDURE_EXAMPLE_CALL_FROM_NESTED |
|-------------------------------------------|
|                                        35 |
+-------------------------------------------+

Using and setting SQL variables in a stored procedure

By default, Snowflake Scripting stored procedures run with owner’s rights. When a stored procedure runs with owner’s rights, it can’t access SQL (or session) variables.

However, a caller’s rights stored procedure can read the caller’s session variables and use them in the logic of the stored procedure. For example, a caller’s rights stored procedure can use the value in a SQL variable in a query. To create a stored procedure that runs with caller’s rights, specify the EXECUTE AS CALLER parameter in the CREATE PROCEDURE statement.

These examples illustrate this key difference between caller’s rights and owner’s rights stored procedures. They attempt to use SQL variables in two ways:

  • Set a SQL variable before calling the stored procedure, then use the SQL variable inside the stored procedure.

  • Set a SQL variable inside the stored procedure, then use the SQL variable after returning from the stored procedure.

Both using the SQL variable and setting the SQL variable work correctly in a caller’s rights stored procedure. Both fail when using an owner’s rights stored procedure, even if the caller is the owner.

For more information about owner’s rights and caller’s rights, see Understanding caller’s rights and owner’s rights stored procedures.

Using a SQL variable in a stored procedure

The following example uses a SQL variable in a stored procedure.

First, set a SQL variable in a session:

SET example_use_variable = 2;
Copy

Create a simple stored procedure that runs with caller’s rights and uses this SQL variable:

CREATE OR REPLACE PROCEDURE use_sql_variable_proc()
RETURNS NUMBER
LANGUAGE SQL
EXECUTE AS CALLER
AS
DECLARE
  sess_var_x_2 NUMBER;
BEGIN
  sess_var_x_2 := 2 * $example_use_variable;
  RETURN sess_var_x_2;
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, the Classic Console, and Python Connector):

CREATE OR REPLACE PROCEDURE use_sql_variable_proc()
RETURNS NUMBER
LANGUAGE SQL
EXECUTE AS CALLER
AS
$$
DECLARE
  sess_var_x_2 NUMBER;
BEGIN
  sess_var_x_2 := 2 * $example_use_variable;
  RETURN sess_var_x_2;
END;
$$
;
Copy

Call the stored procedure:

CALL use_sql_variable_proc();
Copy
+-----------------------+
| USE_SQL_VARIABLE_PROC |
|-----------------------|
|                     4 |
+-----------------------+

Set the SQL variable to a different value:

SET example_use_variable = 9;
Copy

Call the procedure again to see that the returned value has changed:

CALL use_sql_variable_proc();
Copy
+-----------------------+
| USE_SQL_VARIABLE_PROC |
|-----------------------|
|                    18 |
+-----------------------+

Setting a SQL variable in a stored procedure

You can set a SQL variable in a stored procedure that’s running with caller’s rights. For more information, including guidelines for using SQL variables in stored procedures, see Caller’s rights stored procedures.

Note

Although you can set a SQL variable inside a stored procedure and leave it set after the end of the procedure, Snowflake does not recommend doing this.

The following example sets a SQL variable in a stored procedure.

First, set a SQL variable in a session:

SET example_set_variable = 55;
Copy

Confirm the value of the SQL variable:

SHOW VARIABLES LIKE 'example_set_variable';
Copy
+----------------+-------------------------------+-------------------------------+----------------------+-------+-------+---------+
|     session_id | created_on                    | updated_on                    | name                 | value | type  | comment |
|----------------+-------------------------------+-------------------------------+----------------------+-------+-------+---------|
| 10363782631910 | 2024-11-27 08:18:32.007 -0800 | 2024-11-27 08:20:17.255 -0800 | EXAMPLE_SET_VARIABLE | 55    | fixed |         |
+----------------+-------------------------------+-------------------------------+----------------------+-------+-------+---------+

For example, the following stored procedure sets the SQL variable example_set_variable to a new value and returns the new value:

CREATE OR REPLACE PROCEDURE set_sql_variable_proc()
RETURNS NUMBER
LANGUAGE SQL
EXECUTE AS CALLER
AS
BEGIN
  SET example_set_variable = $example_set_variable - 3;
  RETURN $example_set_variable;
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, the Classic Console, and Python Connector):

CREATE OR REPLACE PROCEDURE set_sql_variable_proc()
RETURNS NUMBER
LANGUAGE SQL
EXECUTE AS CALLER
AS
$$
BEGIN
  SET example_set_variable = $example_set_variable - 3;
  RETURN $example_set_variable;
END;
$$
;
Copy

Call the stored procedure:

CALL set_sql_variable_proc();
Copy
+-----------------------+
| SET_SQL_VARIABLE_PROC |
|-----------------------|
|                    52 |
+-----------------------+

Confirm the new value of the SQL variable:

SHOW VARIABLES LIKE 'example_set_variable';
Copy
+----------------+-------------------------------+-------------------------------+----------------------+-------+-------+---------+
|     session_id | created_on                    | updated_on                    | name                 | value | type  | comment |
|----------------+-------------------------------+-------------------------------+----------------------+-------+-------+---------|
| 10363782631910 | 2024-11-27 08:18:32.007 -0800 | 2024-11-27 08:24:04.027 -0800 | EXAMPLE_SET_VARIABLE | 52    | fixed |         |
+----------------+-------------------------------+-------------------------------+----------------------+-------+-------+---------+