Snowflake SQL, a transaction can be started explicitly by executing a BEGIN statement. Snowflake supports the synonyms BEGIN WORK and BEGIN TRANSACTION. Snowflake recommends using BEGIN TRANSACTION.
CREATEORREPLACEPROCEDURE TestTransaction ()RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},{"attributes":{"component":"transact"}}'EXECUTEASCALLERAS$$
BEGIN
DROP TABLE IF EXISTS NEWTABLE;
CREATE OR REPLACE TABLE NEWTABLE (
COL1 INT,
COL2 VARCHAR
);
BEGIN TRANSACTION
!!!RESOLVE EWI!!! /*** SSC-EWI-0101 - COMMENTED OUT TRANSACTION LABEL NAME BECAUSE IS NOT APPLICABLE IN SNOWFLAKE ***/!!!
LabelA;
INSERT INTO NEWTABLE VALUES (1, 'MICHAEL');
INSERT INTO NEWTABLE VALUES(2, 'JACKSON');
COMMIT;
END;
$$;
The CALL statement is not supported in Snowflake Scripting since this is part of the ODBC API and not a SQL statement, therefore this statement is not translated.
Transact-SQL has two possible formats of the Case expression. both of them for the purpose of evaluating expressions and conditionally obtaining results. The first one refers to a Simple Case Expression that will evaluate if an input_expression matches one or more of the when_expression. The second one will evaluate each Boolean_expression independently. The else clause is supported in both formats.
According to the official Transact-SQL Case documentation:
CASE can be used in any statement or clause that allows a valid expression. For example, you can use CASE in statements such as SELECT, UPDATE, DELETE and SET, and in clauses such as select_list, IN, WHERE, ORDER BY, and HAVING.
-- Simple CASE expression:CASE input_expression
WHEN when_expression THEN result_expression [...n ][ELSE else_result_expression ]END-- Searched CASE expression:CASEWHEN boolean_expression THEN result_expression [...n ][ELSE else_result_expression ]END
Note: Transact-SQL allows to optionally encapsulate the input_expression and the boolean_expression in parentheses; Snowflake Scripting too.
CREATEORALTERPROCEDURE SelectCaseDemoProcedure
ASSELECTTOP10
LOGINID,CASE(MARITALSTATUS)WHEN'S'THEN'SINGLE'WHEN'M'THEN'MARIED'ELSE'OTHER'ENDAS status
FROM HUMANRESOURCES.EMPLOYEE;
GO
EXEC SelectCaseDemoProcedure;
CREATEORALTERPROCEDURE SelectCaseDemoProcedure
ASSELECTTOP10
LOGINID,CASEWHEN MARITALSTATUS ='S'THEN'SINGLE'WHEN MARITALSTATUS ='M'THEN'MARIED'ELSE'OTHER'ENDAS status
FROM HUMANRESOURCES.EMPLOYEE;
GO
EXEC SelectCaseDemoProcedure;
Note that in this scenario there are no differences regarding the Case expression itself.
Warning
The declaration and assignment of the res variable is to demonstrate the functional equivalence between both languages. It does not appear in the actual output.
:force:
CREATEORREPLACEPROCEDURE SelectCaseDemoProcedure ()RETURNSTABLE()LANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},{"attributes":{"component":"transact"}}'EXECUTEASCALLERAS$$
DECLARE
ProcedureResultSet RESULTSET;
BEGIN
ProcedureResultSet := (
SELECT TOP 10
LOGINID,
CASE (MARITALSTATUS)
WHEN 'S' THEN 'SINGLE'
WHEN 'M' THEN 'MARIED'
ELSE 'OTHER'
END AS status
FROM
HUMANRESOURCES.EMPLOYEE);
RETURN TABLE(ProcedureResultSet);
END;
$$;CALL SelectCaseDemoProcedure();
CREATEORREPLACEPROCEDURE SelectCaseDemoProcedure ()RETURNSTABLE()LANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},{"attributes":{"component":"transact"}}'EXECUTEASCALLERAS$$
DECLARE
ProcedureResultSet RESULTSET;
BEGIN
ProcedureResultSet := (
SELECT TOP 10
LOGINID,
CASE
WHEN MARITALSTATUS = 'S' THEN 'SINGLE'
WHEN MARITALSTATUS = 'M' THEN 'MARIED'
ELSE 'OTHER'
END AS status
FROM
HUMANRESOURCES.EMPLOYEE);
RETURN TABLE(ProcedureResultSet);
END;
$$;CALL SelectCaseDemoProcedure();
Snowflake Scripting does not allow setting a case expression directly to a variable. Both Transact-SQL Case expression formats translate to the following grammar in Snowflake Scripting.
CREATEORREPLACEPROCEDURE SetCaseDemoProcedure ()RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},{"attributes":{"component":"transact"}}'EXECUTEASCALLERAS$$
DECLARE
VALUE INT;
RESULT INT;
BEGIN
VALUE := 5;
CASE (:VALUE)
WHEN 1 THEN
RESULT := :VALUE * 10;
WHEN 3 THEN
RESULT := :VALUE * 20;
WHEN 5 THEN
RESULT := :VALUE * 30;
WHEN 7 THEN
RESULT := :VALUE * 40;
ELSE
RESULT := -1;
END;
RETURN :RESULT;
END;
$$;DECLARERESULTINT;BEGINCALL SetCaseDemoProcedure();!!!RESOLVE EWI!!!/*** SSC-EWI-0073 - PENDING FUNCTIONAL EQUIVALENCE REVIEW FOR 'Print' NODE ***/!!!
PRINT @result;END;
CREATEORREPLACEPROCEDURE SetCaseDemoProcedure ()RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},{"attributes":{"component":"transact"}}'EXECUTEASCALLERAS$$
DECLARE
VALUE INT;
RESULT INT;
BEGIN
VALUE := 5;
CASE
WHEN :VALUE = 1 THEN
RESULT := :VALUE * 10;
WHEN :VALUE = 3 THEN
RESULT := :VALUE * 20;
WHEN :VALUE = 5 THEN
RESULT := :VALUE * 30;
WHEN :VALUE = 7 THEN
RESULT := :VALUE * 40;
ELSE
RESULT := -1;
END;
RETURN :RESULT;
END;
$$;DECLARERESULTINT;BEGINCALL SetCaseDemoProcedure();!!!RESOLVE EWI!!!/*** SSC-EWI-0073 - PENDING FUNCTIONAL EQUIVALENCE REVIEW FOR 'Print' NODE ***/!!!
PRINT @result;END;
A stored procedure without a body is an unusual scenario that is allowed in Transact-SQL. Snowflake Scripting does not allow defining procedures without a body, but the following example shows the equivalence.
CREATEORALTERPROCEDURE Add_Privacy_Department
ASEXECUTE('INSERT INTO HumanResources.Department VALUES (''Privacy'', ''Executive General and Administration'', default)');
CREATEORREPLACEPROCEDURE Add_Privacy_Department ()RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},"attributes":{"component":"transact"}}'EXECUTEASCALLERAS$$
BEGIN
!!!RESOLVE EWI!!! /*** SSC-EWI-0030 - THE STATEMENT BELOW HAS USAGES OF DYNAMIC SQL. ***/!!!
EXECUTE IMMEDIATE 'INSERT INTO HumanResources.Department VALUES ('Privacy', 'Executive General and Administration', default);';
END;
$$;
You can use parameters to drive your logic or construct dynamic SQL statements inside your stored procedure. In the following example a simple SetNewPrice stored procedure is constructed, which sets a new product price based on the arguments sent by the caller.
Transact-SQL output keyword indicates that the parameter is an output parameter, whose value will be returned to the stored procedure caller. For example, the following procedure will return the number of vacation hours of a specific employee.
CREATEORREPLACEPROCEDURE GetVacationHours (EMPLOYEEID INT, VACATIONHOURS OUTINT)RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},"attributes":{"component":"transact"}}'EXECUTEASCALLERAS$$
BEGIN
SELECT
VacationHours
INTO
:VACATIONHOURS
FROM
HumanResources.Employee
WHERE
NationalIDNumber = :EMPLOYEEID;
END;
$$;
A parameter is considered optional if the parameter has a default value specified when it is declared. It is not necessary to provide a value for an optional parameter in a procedure call.
Transact-SQL’s EXECUTE AS clause defines the execution context of the stored procedure, specifying which user account the Database Engine uses to validate permissions on objects that are referenced within the procedure. For example, we can modify the previous GetVacationHours procedure to define different execution contexts.
CREATEORREPLACEPROCEDURE GetVacationHours (PARAM1 INT!!!RESOLVE EWI!!!/*** SSC-EWI-0058 - FUNCTIONALITY FOR 'READONLY PARAMETERS' IS NOT CURRENTLY SUPPORTED BY SNOWFLAKE SCRIPTING ***/!!!, PARAM2 INT!!!RESOLVE EWI!!!/*** SSC-EWI-0058 - FUNCTIONALITY FOR 'VARYING PARAMETERS' IS NOT CURRENTLY SUPPORTED BY SNOWFLAKE SCRIPTING ***/!!!)RETURNSTABLE()LANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},"attributes":{"component":"transact"}}'EXECUTEASCALLERAS$$
DECLARE
ProcedureResultSet RESULTSET;
BEGIN
ProcedureResultSet := (
SELECT
*
FROM
Table1);
RETURN TABLE(ProcedureResultSet);
END;
$$;
[VARYING] Applies only to cursor parameters.Specifies the result set supported as an output parameter. This parameter is dynamically constructed by the procedure and its contents may vary. Snowflake scripting does not support CURSOR as a valid return data type.
[= default] Makes a parameter optional through the definition of a default value. Snowflake scripting does not natively supports default parameter values.
[READONLY] Indicates that the parameter cannot be updated or modified within the body of the procedure. Currently unsupported in Snowflake Scripting.
[WITH RECOMPILE] Forces the database engine to compile the stored procedure’s query plan each time it is executed. Currently unsupported in Snowflake Scripting.
[WITH ENCRYPTION] Used to encrypt the text of a stored procedure. Only users with access to system tables or database files (such as sysadmin users) will be able to access the procedure text after its creation. Currently unsupported in Snowflake Scripting.
[FOR REPLICATION] Restricts the stored procedure to be executed only during replication. Currently unsupported in Snowflake Scripting.
Transact-SQL statements produce a complete result set, but there are times when the results are best processed one row at a time. Opening a cursor on a result set allows processing the result set one row at a time. You can assign a cursor to a variable or parameter with a cursor data type. For more information, see the Transact-SQL Cursors documentation (https://docs.microsoft.com/en-us/sql/t-sql/language-elements/cursors-transact-sql?view=sql-server-ver15).
[ GLOBAL ] Allows referencing the cursor name in any stored procedure or batch executed by the connection. Snowflake Scripting only allows the use of the cursor locally.
[ SCROLL ] Snowflake Scripting only support FETCH NEXT.
[ KEYSET | DYNAMIC ] If after opening a cursor and update to the table is made, these options may display some of the changes when fetching the cursor, Snowflake scripting only supports STATIC, in other words, after the cursor is opened the changes to the table are not detected by the cursor.
[SCROLL_LOCKS] Specifies that positioned updates or deletes made through the cursor are guaranteed to succeed, Snowflake Scripting cannot guarantee it.
[OPTIMISTIC] When an update or delete is made through the cursor it uses comparisons of timestamp column values, or a checksum value if the table has no timestamp column, to determine whether the row was modified after it was read into the cursor. Snowflake Scripting does not have an internal process to replicate it.
[TYPE_WARNING]
FETCH
[PRIOR | FIRST | LAST] Snowscripting only support NEXT.
[ABSOLUTE] Snowflake Scripting only supports NEXT but the behavior can be replicated.
[RELATIVE] Snowflake Scripting but the behavior can be replicated.
[ GLOBAL ] Allows referencing the cursor name in any stored procedure or batch executed by the connection. Snowflake Scripting only allows the use of the cursor locally.
FETCH without INTO is not supported.
When the FETCH statement is located inside a loop it is considered a complex pattern as it may have an impact on the Snowflake translated code performance. Check the related issues section for more information.
CREATEORREPLACEPROCEDURE cursor_procedure1 ()RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},"attributes":{"component":"transact"}}'EXECUTEASCALLERAS$$
DECLARE
--** SSC-FDM-TS0013 - SNOWFLAKE SCRIPTING CURSOR ROWS ARE NOT MODIFIABLE **
cursor1 CURSOR
FOR
SELECT
col1
FROM
my_table;
BEGIN
WHILE (1=0) LOOP
--** SSC-PRF-0003 - FETCH INSIDE A LOOP IS CONSIDERED A COMPLEX PATTERN, THIS COULD DEGRADE SNOWFLAKE PERFORMANCE. **
FETCH
CURSOR1
INTO
:VARIABLE1;
END LOOP;
END;
$$;
[ GLOBAL ] Allows referencing the cursor name in any stored procedure or batch executed by the connection. Snowflake Scripting only allows the use of the cursor locally.
CLOSE
[ GLOBAL ] Allows referencing the cursor name in any stored procedure or batch executed by the connection. Snowflake Scripting only allows the use of the cursor locally.
DEALLOCATE removes a cursor reference. Snowflake Scripting doesn’t require explicit deallocation because cursors are automatically deallocated when they go out of scope. SnowConvert AI comments out the statement with SSC-FDM-TS0057.
WHERE CURRENT OF the use of this statement is not supported, for example:
:force:
CREATEORALTERPROCEDURE CursorWithCurrent
ASDECLARE@CursorVar CURSOR;SET@CursorVar =CURSORFORSELECT FirstName
FROM vEmployee;OPEN@CursorVar;FETCHNEXTFROM@CursorVar;FETCHNEXTFROM@CursorVar;UPDATE vEmployee SET LastName ='Changed'WHERECURRENTOF@CursorVar;CLOSE@CursorVar;
GO
Transact-SQL allows the creation of table variables that can be used as regular tables. Snowflake scripting does not support this, instead, a table can be created and then dropped at the end of the procedure.
DECLARE statement outside routines (functions and procedures)¶
Unlike Transact-SQL, Snowflake does not support executing isolated statements like DECLARE outside routines like functions or procedures. For this scenario, the statement should be encapsulated in an anonymous block, as shown in the following examples. This statement is usually used before a SET STATEMENT.
If there is a scenario with only DECLARE statements, the BEGIN…END block should have a RETURN NULL statement to avoid errors, since this block can’t be empty.
-- Execute a character string{ EXEC |EXECUTE}({@string_variable |[ N ]'tsql_string'}[+...n ])[AS{ LOGIN |USER}=' name '][;]-- Execute a stored procedure or function[{ EXEC |EXECUTE}]{[@return_status =]{ module_name [;number]|@module_name_var }[[@parameter=]{value|@variable [OUTPUT]|[DEFAULT]}][,...n ][WITH<execute_option>[,...n ]]}[;]
EXECUTE can be used to perform SQL operations passed directly as literals. In the following example it is used within a stored procedure that will insert a new privacy department into the AdventureWorks2019 database.
CREATEORALTERPROCEDURE AddPrivacyDepartment
ASEXECUTE('INSERT INTO HumanResources.Department VALUES (''Privacy'', ''Executive General and Administration'', default)');
CREATEORREPLACEPROCEDURE AddPrivacyDepartment ()RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},"attributes":{"component":"transact"}}'EXECUTEASCALLERAS$$
BEGIN
!!!RESOLVE EWI!!! /*** SSC-EWI-0030 - THE STATEMENT BELOW HAS USAGES OF DYNAMIC SQL. ***/!!!
EXECUTE IMMEDIATE 'INSERT INTO HumanResources.Department VALUES ('Privacy', 'Executive General and Administration', default);';
END;
$$;
EXECUTE can also be used to call an existing stored procedure. The following example will call the AddPrivacyDepartment procedure that was created above. It will then run a SELECT to verify that the new department was successfully included.
Execution of local variable and use of parameters¶
A common use case for the EXECUTE statement is when dynamic SQL statements are needed. In this cases instead of executing a string literal, the statement could be constructed dynamically and assigned to a local variable, which will then be executed. A set of arguments can be sent to the called stored procedure to construct the dynamic SQL command.
In the following example a simple SetNewPrice stored procedure is constructed, which uses the EXECUTE statement to set a new product price based on the arguments sent by the caller. Lastly a SELECT is performed to confirm the new product price.
Transact-SQL EXECUTE syntax contains the @return_status optional argument, which allows creating a scalar variable to store the return status of a scalar-valued user defined function.
It can also be used in stored procedures although the returning status will be limited to integer data type.
To represent this functionality, we could slightly modify the above example and create a user defined function to calculate the new product price as an average of the historical prices. Instead of passing it to the stored procedure, we could now call the CalculateAveragePrice function to obtain the new price, and store it in the return variable to construct the dynamic SQL.
CREATEORALTERFUNCTION CalculateAveragePrice(@pid INT)RETURNS MONEY
ASBEGINDECLARE@average AS MONEY;SELECT@average =AVG(LISTPRICE)FROM Production.ProductListPriceHistory WHERE ProductID =@pid;RETURN@average;END;
GO
CREATEORALTERPROCEDURE SetNewPrice @ProductID INTASDECLARE@averageHistoricalPrice MONEY;EXECUTE@averageHistoricalPrice =[dbo].Calculate_Average_Price @pid=@ProductID;UPDATE Production.ProductListPriceHistory SET ListPrice =@averageHistoricalPrice WHERE ProductID =@ProductID AND EndDate ISNULL;
GO
EXECUTE Set_New_Price @ProductID =707;SELECT ListPrice FROM Production.ProductListPriceHistory WHERE ProductID =707AND EndDate ISNULL;
CREATEORREPLACEFUNCTION CalculateAveragePrice (PID INT)RETURNSNUMBER(38,4)LANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},"attributes":{"component":"transact"}}'AS$$
WITH CTE1 AS
(
SELECT
AVG(LISTPRICE) AS AVERAGE FROM
Production.ProductListPriceHistory
WHERE
ProductID = PID
)
SELECT
AVERAGE
FROM
CTE1
$$;CREATEORREPLACEPROCEDURE SetNewPrice (PRODUCTID INT)RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},"attributes":{"component":"transact"}}'EXECUTEASCALLERAS$$
DECLARE
AVERAGEHISTORICALPRICE NUMBER(38, 4);
BEGIN
CALL dbo.Calculate_Average_Price(:PRODUCTID);
UPDATE Production.ProductListPriceHistory
SET
ListPrice = :AVERAGEHISTORICALPRICE
WHERE
ProductID = :PRODUCTID
AND EndDate IS NULL;
END;
$$;CALL Set_New_Price(707);SELECT
ListPrice
FROM
Production.ProductListPriceHistory
WHERE
ProductID =707AND EndDate ISNULL;
The IF clause allows an SQL statement or a block of statements to be conditionally executed as long as the Boolean expression is true; otherwise, the statements in the optional ELSE clause will be executed. Transact-SQL also supports embedding multiple IF… ELSE clauses in case multiple conditions are required, or the CASE clause can also be used.
The following code refers to an IF… ELSE in Transact-SQL that conditions the variable @value to identify if it is less than 5, if it is between 5 and 10, or if it has any other value. Since @value is initialized as 7, the second condition must be true and the result must be 200.
Notice that in Snowflake Scripting, the embedded IF… ELSE condition is called ELSEIF.
Besides, the Boolean condition is encapsulated in parentheses and the clause always ends with the END IF expression.
In addition, in Snowflake Scripting it is not necessary to use the BEGIN and END keywords to define a statement block, however it can be used if required.
CREATEORREPLACEPROCEDURE IfElseDemoProcedure ()RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},{"attributes":{"component":"transact"}}'EXECUTEASCALLERAS$$
DECLARE
VALUE INT;
BEGIN
VALUE := 7;
IF (:VALUE < 5) THEN
VALUE := 100;
ELSEIF (:VALUE >= 5 AND :VALUE < 10) THEN
BEGIN
VALUE := 300;
VALUE := :VALUE - 100;
END;
ELSE
VALUE := -1;
END IF;
RETURN :VALUE;
END;
$$;DECLARERESULTINT;BEGINCALL IfElseDemoProcedure();!!!RESOLVE EWI!!!/*** SSC-EWI-0073 - PENDING FUNCTIONAL EQUIVALENCE REVIEW FOR 'Print' NODE ***/!!!
PRINT @result;END;
IF statement outside routines (functions and procedures)¶
Unlike Transact-SQL, Snowflake does not support executing isolated statements like IF…ELSE outside routines like functions or procedures. For this scenario, the statement should be encapsulated in an anonymous block, as shown in the following example.
You can read more about how to correctly return the output values in the SELECT section.
DECLARE@maxWeight FLOAT,@productKey INTEGERSET@maxWeight =100.00SET@productKey =424IF@maxWeight <=99SELECT@productKey,'This product is too heavy to ship and is only available for pickup.'ELSESELECT@productKey,'This product is available for shipping or pickup.'
DECLARE
MAXWEIGHT FLOAT;
PRODUCTKEY INTEGER;
BlockResultSet1 VARCHAR;
BlockResultSet2 VARCHAR;
return_arr ARRAY:=array_construct();BEGIN
MAXWEIGHT :=100.00;
PRODUCTKEY :=424;IF(:MAXWEIGHT<=99)THEN
BlockResultSet1 :='RESULTSET_'||REPLACE(UPPER(UUID_STRING()),'-','_');CREATEORREPLACETEMPORARYTABLEIDENTIFIER(:BlockResultSet1)ASSELECT:PRODUCTKEY,'This product is too heavy to ship and is only available for pickup.';
return_arr :=array_append(return_arr,:BlockResultSet1);ELSE
BlockResultSet2 :='RESULTSET_'||REPLACE(UPPER(UUID_STRING()),'-','_');CREATEORREPLACETEMPORARYTABLEIDENTIFIER(:BlockResultSet2)ASSELECT:PRODUCTKEY,'This product is available for shipping or pickup.';
return_arr :=array_append(return_arr,:BlockResultSet2);ENDIF;--** SSC-FDM-0020 - MULTIPLE RESULT SETS ARE RETURNED IN TEMPORARY TABLES **RETURN return_arr;END;
T-SQL supports GOTO for unconditional jumps to labeled statements within a procedure. Snowflake Scripting does not support GOTO or labeled jump targets natively.
When GOTO/Label patterns appear inside a stored procedure with forward-only jumps to top-level labels, SnowConvert AI automatically transforms them using a nested procedure decomposition approach. The procedure body is split into sections at each label declaration, and each section becomes its own nested procedure — code before the first label is placed in a nested procedure called SC_PROCESS, while each labeled section becomes a nested procedure named after its label. Every GOTO label is then replaced with CALL label(); RETURN 'PROCESS FINISHED';, which transfers control to the target section and exits the current one. To preserve sequential execution when no GOTO is taken, each section automatically calls the next one at its end (fall-through). All local variable declarations are moved up to the parent procedure’s DECLARE block so that every nested procedure can access them through Snowflake’s lexical scoping. Any RETURN @value inside a label section is translated to an assignment SC_EXIT_CODE := :expr;, and the outer procedure body simply calls SC_PROCESS() and then returns the exit code.
When the pattern cannot be transformed, the original GOTO and label statements are preserved with EWI markers. This happens with backward GOTOs (where the target label appears before the GOTO in source order, which would require recursive nested calls), GOTO/Label in anonymous blocks or UDFs (which do not support nested procedure definitions), and labels inside nested control flow such as IF, WHILE, or TRY blocks (which cannot be extracted into top-level nested procedures).
A common T-SQL pattern uses GOTO to skip to a cleanup or exit section when an error is detected. SnowConvert AI transforms this by wrapping the main logic and the cleanup label into separate nested procedures.
When a procedure has multiple labeled sections, SnowConvert AI preserves sequential fall-through by having each nested procedure call the next one at its end. A GOTO can also skip ahead to any label, bypassing intermediate sections.
CREATEORREPLACEPROCEDURE dbo.ProcessShipment (STATUS OUTSTRING)RETURNSVARCHARLANGUAGESQLEXECUTEASCALLERAS$$
DECLARE
SC_EXIT_CODE VARCHAR;
SC_PROCESS PROCEDURE ()
RETURNS VARCHAR
AS
BEGIN
STATUS := 'Received';
IF (:STATUS = 'skip') THEN
BEGIN
CALL Ship();
RETURN 'PROCESS FINISHED';
END;
END IF;
CALL Validate();
END;
Validate PROCEDURE ()
RETURNS VARCHAR
AS
BEGIN
STATUS := 'Validated';
CALL Pack();
END;
Pack PROCEDURE ()
RETURNS VARCHAR
AS
BEGIN
STATUS := 'Packed';
CALL Ship();
END;
Ship PROCEDURE ()
RETURNS VARCHAR
AS
BEGIN
STATUS := 'Shipped';
SC_EXIT_CODE := 0;
END;
BEGIN
CALL SC_PROCESS();
RETURN :SC_EXIT_CODE;
END;
$$;
GOTO statements inside nested IF or BEGIN...END blocks are also transformed. The CALL/RETURN pair exits from any depth of nesting, effectively reproducing the jump-out behavior of the original GOTO.
CREATEORREPLACEPROCEDURE dbo.ApproveExpenseReport (MANAGERAPPROVED INT, BUDGETAVAILABLE INT)RETURNSVARCHARLANGUAGESQLEXECUTEASCALLERAS$$
DECLARE
SC_EXIT_CODE VARCHAR;
APPROVALSTATUS INT := 0;
SC_PROCESS PROCEDURE ()
RETURNS VARCHAR
AS
BEGIN
IF (:MANAGERAPPROVED = 1) THEN
BEGIN
IF (:BUDGETAVAILABLE = 1) THEN
BEGIN
CALL Finalize();
RETURN 'PROCESS FINISHED';
END;
END IF;
APPROVALSTATUS := 1;
END;
END IF;
APPROVALSTATUS := 2;
CALL Finalize();
END;
Finalize PROCEDURE ()
RETURNS VARCHAR
AS
BEGIN
SC_EXIT_CODE := 0;
END;
BEGIN
CALL SC_PROCESS();
RETURN :SC_EXIT_CODE;
END;
$$;
When a GOTO targets a label that appears before the GOTO in the source (a backward jump), SnowConvert AI cannot apply the nested procedure decomposition because it would require recursive calls, which Snowflake does not support for nested procedures. In these cases, the GOTO and label are preserved with EWI markers for manual resolution.
CREATEORREPLACEPROCEDURE dbo.RetryDatabaseConnection ()RETURNSVARCHARLANGUAGESQLEXECUTEASCALLERAS$$
DECLARE
ATTEMPTS INT := 0;
BEGIN
!!!RESOLVE EWI!!! /*** SSC-EWI-TS0045 - LABELED STATEMENT IS NOT SUPPORTED IN SNOWFLAKE SCRIPTING ***/!!!
RetryConnection:
ATTEMPTS := :ATTEMPTS + 1;
IF (:ATTEMPTS < 3) THEN
!!!RESOLVE EWI!!! /*** SSC-EWI-TS0087 - GOTO IS NOT SUPPORTED IN SNOWFLAKE. ***/!!!
GOTO RetryConnection
END IF;
RETURN 0;
END;
$$;
LABEL and GOTO outside stored procedures (not transformed)¶
When GOTO and labels appear in batch scripts outside of a stored procedure, the nested procedure decomposition cannot be applied because Snowflake anonymous blocks do not support nested procedure definitions. The statements are preserved with EWI markers.
An output parameter is a parameter whose value is passed out of the stored procedure, back to the calling SQL block. Since the output parameters are not supported by Snowflake Scripting, a solution has been implemented to emulate their functionality.
The most basic scenario for OUT parameters is when the procedure only has one. In this case, we simply return the OUT parameter at the end of the procedure body.
The EXEC procedure has to be translated as well, for this a CALL is created, the parameters are passed without any modifier (“OUT” is removed), and subsequently, an assignment is done so the parameter is associated with it’s respective resulting value.
-- Procedure with output parameterCREATEPROCEDURE dbo.outmain
@nameVARCHAR(255)OUTPUTASSET@name='Jane';
GO
-- Auxiliary procedure that calls the main procedureCREATEPROCEDURE dbo.outaux
ASDECLARE@nameVARCHAR(255);
EXEC dbo.outmain
@name=@nameOUTPUT;
When more than one OUT parameters are found, the RETURNS clause of the procedure changes to VARIANT. This is to accommodate the OBJECT_CONSTRUCT that is going to be used to store the values of the OUT parameters.
On top of that, a RETURN statement is added to the end of the procedure’s body. This is where the OBJECT_COSNTRUCT is created and all the OUT parameter values are stored within it. This object will then be used by the caller to assign the parameters value to the corresponding result.
Transact-SQL allows procedures to have return values. When a procedure has both a return value and OUT parameter(s), a similar approach to the Multiple OUT parameters scenario is followed. The original return value is treated as an OUT parameter would be treated, so it’s stored within the OBJECT_CONSTRUCT and extracted inside the caller procedure.
-- Procedure with multiple output parametersCREATEPROCEDURE dbo.outmain
@nameVARCHAR(255)OUTPUTASSET@name='Jane';RETURN0;
GO
-- Auxiliary procedure that calls the main procedureCREATEPROCEDURE dbo.outaux
ASDECLARE@nameVARCHAR(255);DECLARE@returnValue INT;
EXEC @returnValue = dbo.outmain
@name=@nameOUTPUT;
CREATEORALTERPROCEDURE SetProcedure
ASDECLARE@MyCounter INT;DECLARE@FloatCounter FLOAT;--Numerical operatorsSET@MyCounter =3;SET@MyCounter +=1;--@MyCounter has 4SET@MyCounter -=1;--@MyCounter has 3SET@MyCounter *=2;--@MyCounter has 6SET@MyCounter /=3;--@MyCounter has 2SET@MyCounter =6;SET@MyCounter /=5;--@MyCounter has 1SET@MyCounter =6;SET@MyCounter /=7;--@MyCounter has 0SET@FloatCounter =10;SET@FloatCounter /=4;--@FloatCounter has 2.5SET@MyCounter =6;SET@MyCounter %=4;--@MyCounter has 2--Logical operatorsSET@MyCounter &=3;--@MyCounter has 2SET@MyCounter ^=2;--@MyCounter has 0SET@MyCounter |=0;--@MyCounter has 0RETURN@MyCounter;
GO
DECLARE@resultINT;
EXEC @result= SetProcedure;
PRINT @result;
SET statement outside routines (functions and procedures)¶
Unlike Transact-SQL, Snowflake does not support executing isolated statements like SET outside routines like functions or procedures. For this scenario, the statement should be encapsulated in an anonymous block, as shown in the following examples. This statement is usually used after a DECLARE STATEMENT.
If there is a scenario with only SET statements, the DECLARE block is not necessary. Probably this scenario will produce runtime errors if there is an attempt of setting a value to a variable that is not declared.
Implements error handling for Transact SQL. A group of Transact-SQL statements can be enclosed in a TRY block. If an error occurs in the TRY block, control is usually passed to another group of statements that is enclosed in a CATCH block.
DECLARE
BlockResultSet1 VARCHAR;
BlockResultSet2 VARCHAR;
return_arr ARRAY:=array_construct();BEGINBEGIN
BlockResultSet1 :='RESULTSET_'||REPLACE(UPPER(UUID_STRING()),'-','_');CREATEORREPLACETEMPORARYTABLEIDENTIFIER(:BlockResultSet1)ASSELECTTRUNC(1/0);
return_arr :=array_append(return_arr,:BlockResultSet1);EXCEPTIONWHEN OTHER THEN
BlockResultSet2 :='RESULTSET_'||REPLACE(UPPER(UUID_STRING()),'-','_');CREATEORREPLACETEMPORARYTABLEIDENTIFIER(:BlockResultSet2)ASSELECT'error';
return_arr :=array_append(return_arr,:BlockResultSet2);END;--** SSC-FDM-0020 - MULTIPLE RESULT SETS ARE RETURNED IN TEMPORARY TABLES **RETURN return_arr;END;
The While statement allows an SQL statement or a block of statements to be repeatedly executed as long as the specified condition is true. The execution of statements in the WHILE loop can be controlled from inside the loop with the BREAK and CONTINUE keywords.
The following code refers to a While Loop in Transact-SQL that iterates the @Iteration variable and controls the flow of the loop to terminate when the value of @Iteration equals 10.
Note
Statements after the CONTINUE keyword will not be executed.
As well as Transact-SQL, in Snowflake Scripting the statements after the CONTINUE keyword will not be executed.
Notice that in Snowflake Scripting it is not necessary to use the BEGIN and END keywords to define a statement block, however it can be used if required.
Please note this example was written while the IF ELSE statement was not supported, the differences in the results should disappear when support for the statement is implemented.
This statement can not have an empty body in Snowflake Scripting, to solve this cases a default BREAK statement is added when an empty body is detected.
CREATEORREPLACEPROCEDURE WhileEmptyBodyProc ()RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},{"attributes":{"component":"transact"}}'EXECUTEASCALLERAS$$
DECLARE
MYVAR INT;
RESULT INT;
BEGIN
BEGIN
MYVAR := 1;
WHILE (:MYVAR < 100) LOOP
IF (:MYVAR < 50) THEN
MYVAR := MYVAR * 5;
ELSE
MYVAR := MYVAR * 3;
END IF;
END LOOP;
RETURN :MYVAR;
END;
CALL WhileEmptyBodyProc();
!!!RESOLVE EWI!!! /*** SSC-EWI-0073 - PENDING FUNCTIONAL EQUIVALENCE REVIEW FOR 'PRINT' NODE ***/!!!
PRINT @result;
END;
$$;
WHILE statement outside routines (functions and procedures)¶
Unlike Transact-SQL, Snowflake does not support executing isolated statements like WHILE outside routines like functions or procedures. For this scenario, the statement should be encapsulated in an anonymous block, as shown in the following example.