SnowConvert - Translation Spec
  • For Teradata
  • Translation Reference
    • Teradata
      • Scripts to Snowflake SQL Translation Reference
        • COMMON STATEMENTS
          • ERROR HANDLING
          • EXIT or QUIT
          • GOTO
          • IF... THEN...
        • BTEQ
        • MLOAD
          • Import
      • Scripts To Python Translation Reference
        • BTEQ
          • REPEAT
          • USING REQUEST MODIFIER
        • FLOAD
          • BEGIN LOADING
        • MLOAD
          • BEGIN MLOAD
        • TPT
        • SnowConvert Scripts Helpers
          • Technical Documentation
      • SQL Translation Reference
        • Built-in Functions
          • CURRENT_TIMESTAMP
          • Cast to DATE using { }
          • Cast to INTERVAL datatype
          • COALESCE
          • DAYNUMBER_OF_MONTH
          • FROM_BYTES
          • GETQUERYBANDVALUE
          • JSON_CHECK
          • JSON_EXTRACT
          • JSON_TABLE
          • NEW JSON
          • NVP
          • OVERLAPS
          • PIVOT
          • P_INTERSECT
          • RANK
          • Regex functions
          • STRTOK_SPLIT_TO_TABLE
          • SUBSTRING
          • TD_UNPIVOT
          • TO_CHAR
          • XMLAGG
        • Database DBC
        • Data Types
        • DDL
          • Tables
            • WITH DEFAULT
          • Index
          • Views
        • DML
          • Delete Statement
          • Insert Statement
            • LOGGING ERRORS
          • Select Statement
            • Expand On Clause
            • Normalize
            • Reset When
          • Set Operators
          • Update Statement
          • With Modifier
        • SnowConvert Procedures Helpers
          • Cursor Helper
          • Exec Helper
          • Functional Equivalence Helpers
          • Into Helper
      • Teradata to JavaScript Translation Reference
        • GET DIAGNOSTICS EXCEPTION
        • Macros
        • Procedures
      • Teradata to Snowflake Scripting Translation Reference
        • ABORT and ROLLBACK
        • BEGIN END
        • CASE
        • CREATE MACRO
        • CREATE PROCEDURE
        • CURSOR
        • DECLARE
        • DECLARE CONDITION HANDLER
        • DML and DDL Objects
        • EXCEPTION HANDLERS
        • EXECUTE IMMEDIATE
        • EXECUTE/EXEC
        • FUNCTION OPTIONS OR DATA ACCESS
        • GET DIAGNOSTICS EXCEPTION
        • IF
        • LOCKING FOR ACCESS
        • LOOP
        • OUTPUT PARAMETERS
        • PREPARE
        • REPEAT
        • SET
        • SYSTEM_DEFINED
        • WHILE
    • Oracle
      • Sample data
      • Basic Elements of Oracle SQL
        • Data Types
          • Oracle Built-in Data Types
            • Character Data Types
              • CHAR Data type
              • NCHAR Data Type
              • VARCHAR2 Data Type
              • VARCHAR Data Type
              • NVARCHAR2 Data Type
            • Numeric Data Types
              • NUMBER Data Type
              • FLOAT Data Type
              • Floating-Point Numbers
                • BINARY_FLOAT
                • BINARY_DOUBLE
            • LONG Data Type
            • Datetime and Interval Data Types
              • DATE Data Type
              • TIMESTAMP Data Type
              • TIMESTAMP WITH TIME ZONE Data Type
              • TIMESTAMP WITH LOCAL TIME ZONE Data Type
              • INTERVAL YEAR TO MONTH Data Type
              • INTERVAL DAY TO SECOND Data Type
              • Datetime Arithmetic
                • Interval UDFs vs Snowflake native interval operation
            • LOB Data Types
              • BFILE Data Type
              • BLOB Data Type
              • CLOB Data Type
              • NCLOB Data type
            • JSON Data Type
            • Extended Data Types
            • RAW and LONG RAW Data types
            • PL SQL Data Types
              • PLS_INTEGER Data Type
              • BINARY_INTEGER Data Type
          • Rowid Data Type
            • ROWID DataType
            • UROWID Data Type
          • ANSI Data Types
          • User-Defined Types
            • REF Data Types
          • Any Types
            • ANYTYPE
            • ANYDATA
            • ANYDATASET
          • XML Types
            • XMLType
            • URI Data Types
              • HTTPURIType
              • XDBURIType
              • DBURIType
            • URIFactory Package
          • Spatial Types
            • SDO_GEOMETRY
            • SDO_TOPO_GEOMETRY
            • SDO_GEORASTER
        • Literals
          • Interval Literal
          • Interval Type and Date Type
          • Text literals
      • Pseudocolumns
        • ROWID
        • ROWNUM
      • Built-in functions
        • SnowConvert Custom UDFs
          • BFILENAME UDF
          • DATE_TO_JULIANDAYS_UDF
          • DATEADD UDF
          • DATEDIFF UDF
          • INTERVAL UDFs
            • DATEADD UDF INTERVAL
            • DATEDIFF UDF INTERVAL
          • CAST_DATE UDF
          • JSON_VALUE UDF
          • JULIAN TO GREGORIAN DATE UDF
          • MONTHS BETWEEN UDF [DEPRECATED]
          • REGEXP LIKE UDF
          • TIMESTAMP DIFF UDF
          • TRUNC (date) UDF
          • TRUNC (number) UDF
        • TO_NUMBER
        • NLSSORT
      • Built-In packages
        • DBMS_LOB
          • SUBSTR Function
        • DBMS_RANDOM
          • VALUE functions
        • DBMS_OUTPUT
          • PUT_LINE procedure
        • UTL_FILE
          • FOPEN procedure
          • PUT_LINE procedure
          • FCLOSE procedure
      • SQL Queries and Subqueries
        • Select
          • Select Flashback Query
        • Joins
          • Equijoin
          • Band Join
          • Self Join
          • Cartesian Products
          • Inner Join
          • Outer Join
          • Antijoin
          • Semijoin
      • SQL Statements
        • Alter Session
        • Alter Table
        • Create Materialized Views
        • Create Database Link
        • Create Index
        • Create Sequence
        • Create Synonym
        • Create Table
        • Create Type
          • Object Type Definition
          • Subtype Definition
          • Array Type Definition
          • Nested Table Type Definition
          • Member Function Definitions
        • Create View
        • Drop Table
      • PL/SQL to Snowflake Scripting
        • ASSIGNMENT STATEMENT
        • CALL
        • CASE
        • COLLECTIONS AND RECORDS
          • Associative Array Type Definition
          • Varray Type Definition
          • Nested Table Array Type Definition
          • Collection Methods
          • Collection Bulk Operations
            • WITH, SELECT, and BULK COLLECT INTO statements
          • Record Type Definition
        • COMPOUND STATEMENTS
        • CONTINUE
        • CREATE PROCEDURE
        • CURSOR
          • PARAMETRIZED CURSOR
          • CURSOR DECLARATION
          • Workaround for cursors using parameters or procedure variables
          • Cursor Variables
        • DECLARE
        • DEFAULT PARAMETERS
        • DML STATEMENTS
          • INSERT Statement Extension
          • MERGE Statement
          • SELECT INTO Statement
          • Work around to simulate the use of Records
        • EXIT
        • EXPRESSIONS
        • EXECUTE IMMEDIATE
        • FORALL
        • FOR LOOP
        • HELPERS
          • Bulk Cursor Helpers
        • IF
        • IS EMPTY
        • LOCK TABLE
        • LOG ERROR
        • LOOP
        • OUTPUT PARAMETERS
        • PACKAGES
          • DECLARATION
          • BODY
          • VARIABLES
          • Constants
        • PROCEDURE CALL
        • RAISE
        • RAISE_APPICATION_ERROR
        • UDF CALL
        • WHILE
      • PL/SQL to Javascript
        • Procedures
        • User defined functions
        • Packages
        • Helpers
          • EXEC Helper
          • Cursor Helper
          • Raise Helper
          • ROWTYPE Helper
          • Between operator helper
          • Like operator Helper
          • IS NULL Helper
          • Concat Value Helper
          • Package variables helper
          • Implicit Cursor attribute helper
        • Declarations
        • Control Statements
        • Conditional Compilation
        • Collections & Records
        • DDL - DML Statements
        • SQL Language Elements
        • Expressions and operators
        • Synonyms
        • Triggers
        • TYPE attribute
      • SQL*Plus
        • Archive Log
        • Attribute
        • Break
        • Btitle
        • Change
        • Column
        • Define
        • Host
        • Prompt
        • Remark
        • Set
        • Spool
        • Start
        • Whenever oserror
        • Whenever sqlerror
        • Show
        • Append
        • Accept
      • Wrapped objects
    • SQLServer
      • General Language Elements
        • COMPUTED COLUMN
        • EXECUTE
          • System Store Procedures
            • SP_RENAME
        • Collate
        • USE
        • OUTER APPLY
      • DDLs
        • Tables
        • Index
        • Views
        • Procedures
        • Functions
      • DMLs
        • Set Operators
        • Between
        • Update
        • Select
        • Insert
        • Delete
        • Merge
        • Exists
        • IN
        • Truncate
        • Drop
        • Bulk Insert
        • Common Table Expression (CTE)
        • Drops
      • Data Types
      • Statements
        • ALTER
          • TABLE
            • ADD
              • COLUMN DEFINITION
                • COLUMN CONSTRAINT
                  • FOREIGN KEY
                  • PRIMARY KEY / UNIQUE
                  • CHECK
              • TABLE CONSTRAINT
                • FOREIGN KEY
                • PRIMARY KEY
                • CHECK CONSTRAINT
                • CHECK
                • CONNECTION
                • DEFAULT
                • ON PARTITION
        • CREATE
          • FUNCTION
            • SCALAR
            • INLINE TABLE-VALUED
            • MULTI-STATEMENT TABLE-VALUED
      • Built-in functions
        • SnowConvert custom UDFs
          • OPENXML UDF
          • STR UDF
          • SWITCHOFFSET_UDF
        • Aggregate functions
          • COUNT
          • COUNT_BIG
          • SUM
        • Analytic Functions
          • LAG
        • Conversion functions
          • CONVERT
          • TRY_CONVERT
        • Data Type functions
          • DATALENGTH
        • Date & Time functions
          • AT TIME ZONE
          • SWITCHOFFSET
          • DATEADD
          • DATEDIFF
          • DATEPART
          • DATEFROMPARTS
          • DATENAME
          • DAY
          • EOMONTH
          • GETDATE
          • MONTH
          • SYSDATETIME
          • SYSUTCDATETIME
          • YEAR
        • Logical functions
          • IIF
        • Mathematical functions
          • ABS
          • ACOS
            • ACOS in JS
          • ASIN
            • ASIN in JS
          • ATAN
            • ATAN in JS
          • ATN2
            • ATAN2 in JS
          • AVG
          • CEILING
          • COS
            • COS in JS
          • COT
            • COT in JS
          • DEGREES
            • DEGREES in JS
          • EXP
            • EXP in JS
          • FLOOR
          • LOG
            • LOG in JS
          • LOG10
            • LOG10 in JS
          • PI
            • PI in JS
          • POWER
            • POW in JS
          • SQUARE
          • STDEV
          • STDEVP
          • VAR
          • POWER
          • RADIANS
            • RADIANS in JS
          • ROUND
          • SQRT
        • Metadata functions
          • DB_NAME
          • OBJECT_ID
        • Ranking functions
          • DENSE_RANK
          • RANK
          • ROW_NUMBER
        • String functions
          • ASCII
            • ASCII in JS
          • CHAR
          • CHARINDEX
          • COALESCE
          • CONCAT
          • CONCAT_WS
            • Join in JS
          • DIFFERENCE
            • DIFFERENCE in JS
          • FORMAT
            • FORMAT in JS
          • LEFT
          • LEN
          • LOWER
          • LTRIM
            • LTRIM in JS
          • NCHAR
          • PATINDEX
            • search in JS
          • QUOTENAME
            • QUOTENAME in JS
          • REPLACE
          • REPLICATE
          • REVERSE
            • reverse in JS
          • RIGHT
          • RTRIM
          • SOUNDEX
            • SOUNDEX in JS
          • SPACE
          • STR
            • STR in JS
          • STRING_ESCAPE
            • stringify in JS
          • SUBSTRING
          • TRIM
            • trim in JS
          • UPPER
        • System functions
          • FORMATMESSAGE
            • FORMATMESSAGE_UDF
          • ISNULL
          • NEWID
          • NULLIF
        • XML Functions
          • Value
          • Query
      • Built-in procedures
        • Custom User Defined Procedures
          • SP_ADDEXTENDEDPROPERTY_UDP
      • Snowflake Scripting
        • CREATE PROCEDURE
        • CASE
        • CURSOR
        • DECLARE
        • EXECUTE
        • IF
        • SET
        • DMLs
        • CALL
        • WHILE
        • DROPs
        • BEGIN and COMMIT Transaction
        • OUTPUT PARAMETERS
        • LABEL and GOTO
        • SELECT
        • TEXTIMAGE_ON
        • TRY CATCH
      • System Tables
        • SYS.FOREIGN_KEYS
      • Queries
        • TOP
Powered by GitBook
On this page
  • Description
  • SQL Server Syntax
  • Snowflake SQL
  • Sample Source Patterns
  • Insert values in a table
  • Insert value according to if/else statement
  • Inserts multiple according to if/else statement
  • Update values previously inserted
  • Multiple return clauses
  • Complex cases
  • Known Issues
  • While statements along side queries
  • Declare Cursor
  • Different statements are not supported in Common Tables Expressions
  • Related EWIs
  1. Translation Reference
  2. SQLServer
  3. Statements
  4. CREATE
  5. FUNCTION

MULTI-STATEMENT TABLE-VALUED

Translation reference to convert SQL Server UDF (User Defined Functions) with TABLE return type to Snowflake.

PreviousINLINE TABLE-VALUEDNextBuilt-in functions

Last updated 1 year ago

The transformation for this page has not been implemented yet in SnowConvert. These translations may change in the future.

Description

Multi-statement table-valued is similar to Inline-statement table-valued (). However Multi-statement table-valued may have more than one statement in its function body, the table columns are specified in the return type and it has a BEGIN/END block (

SQL Server Syntax

CREATE [ OR ALTER ] FUNCTION [ schema_name. ] function_name
( [ { @parameter_name [ AS ] [ type_schema_name. ] parameter_data_type
    [ = default ] [READONLY] }
    [ ,...n ]
  ]
)
RETURNS @return_variable TABLE <table_type_definition>
    [ WITH <function_option> [ ,...n ] ]
    [ AS ]
    BEGIN
        function_body
        RETURN
    END
[ ; ]

Snowflake SQL

CREATE OR REPLACE FUNCTION <name> ( [ <arguments> ] )
  RETURNS TABLE ( <output_col_name> <output_col_type> [, <output_col_name> <output_col_type> ... ] )
  AS '<sql_expression>'

Sample Source Patterns

The following section describes all the possible source code patterns that can appear in this kind of CREATE FUNCTION syntax.

The function body of Multi-Statement Table-Valued function must be a SELECT statement. For this reason the others statements must be called separately.

Insert values in a table

Inserts one or more rows into the table and returns the table with the new values

SQL Server

IN -> SqlServer_01.sql
CREATE OR ALTER FUNCTION calc_behavioral_segment()
RETURNS @behavioral_segments TABLE (behavioral_segment VARCHAR(50))
AS
BEGIN
	DECLARE @col varchar(15)
	SET @col = 'Unknown'
	INSERT INTO @behavioral_segments 
	SELECT @col
	
	RETURN 
END

SELECT * FROM calc_behavioral_segment();
BEHAVIORAL_SEGMENT|
------------------+
           Unknown|

Snowflake SQL

OUT -> SqlServer_01.sql
CREATE OR REPLACE FUNCTION calc_behavioral_segment()
RETURNS TABLE(behavioral_segment VARCHAR(50))
AS
$$     
     WITH CTE AS (SELECT 'Unknown' as behavior)
     SELECT behavior FROM CTE
$$;

SELECT * FROM TABLE(calc_behavioral_segment());
BEHAVIORAL_SEGMENT|
------------------+
           Unknown|

Insert value according to if/else statement

Inserts a row into the table according to the condition and returns the table with the new value

SQL Server

IN -> SqlServer_02.sql
CREATE OR ALTER FUNCTION odd_or_even_number(@number INT)
RETURNS @numbers TABLE (number_type VARCHAR(15))
AS
BEGIN 
	IF ((@number % 2) = 0)
	BEGIN
		INSERT @numbers SELECT 'Even'
	END
		  
	ELSE
	BEGIN
		INSERT @numbers SELECT 'Odd'
	END

	RETURN
END

SELECT * FROM odd_or_even_number(9);
       NUMBER_TYPE|
------------------+
               Odd|

Snowflake SQL

OUT -> SqlServer_02.sql
CREATE OR REPLACE FUNCTION odd_or_even_number(number int)
RETURNS TABLE(number_type VARCHAR(50))
AS
$$
     WITH CTE2 as (SELECT CASE WHEN (number % 2) = 0 THEN 'EVEN'
     ElSE 'ODD'
     END AS result)
     SELECT result FROM CTE2
$$;

select * from table(odd_or_even_number(9));
       NUMBER_TYPE|
------------------+
               Odd|

Inserts multiple according to if/else statement

The example below inserts more than one value into the table and more than one variable is modified according to the condition. Returns the table with the new values

SQL Server

IN -> SqlServer_03.sql
CREATE OR ALTER FUNCTION new_employee_hired(@id VARCHAR (50), @position VARCHAR(50), @experience VARCHAR(15))
RETURNS @new_employee TABLE (id_employee VARCHAR (50), working_from_home BIT, team VARCHAR(15), computer VARCHAR(15)) 
AS
BEGIN 
	DECLARE @wfh BIT
	DECLARE @team VARCHAR(15)
	DECLARE @computer VARCHAR(15)

	IF @position = 'DEVELOPER'
	BEGIN
		SET @team = 'TEAM_1'
		SET @computer = 'LAPTOP'		
	END

	IF @position = 'IT'
	BEGIN		
		SET @team = 'TEAM_2'
		SET @computer = 'DESKTOP'
	END

	IF @experience = 'JUNIOR'
	BEGIN
		SET @wfh = '0'			
	END
	IF @experience = 'SENIOR'
	BEGIN
		SET @wfh = '1'						
	END

	INSERT INTO @new_employee VALUES (@id, @wfh, @team, @computer)
	RETURN
END

SELECT * FROM new_employee_hired('123456789', 'DEVELOPER', 'SENIOR');
   ID_EMPLOYEE|  WORKING_FROM_HOME|     TEAM|   COMPUTER|
--------------+-------------------|---------|-----------|
     123456789|                  1|   TEAM_1|     LAPTOP|

Snowflake

OUT -> SqlServer_03.sql
CREATE OR REPLACE FUNCTION new_employee_hired(id VARCHAR (50), position VARCHAR(50), experience VARCHAR(15))
RETURNS TABLE (id_employee VARCHAR (50), working_from_home BOOLEAN, team VARCHAR(15), computer VARCHAR(15))
AS
$$
    WITH CTE1 AS (SELECT CASE 
            WHEN POSITION = 'DEVELOPER' THEN 'TEAM_1'
            WHEN POSITION = 'IT' THEN  'TEAM_2'
            END),
          CTE2 AS (SELECT CASE 
            WHEN POSITION = 'DEVELOPER' THEN 'LAPTOP'
            WHEN POSITION = 'IT' THEN  'DESKTOP'
            END),
          CTE3 AS (SELECT CASE 
            WHEN EXPERIENCE = 'JUNIOR' THEN FALSE
            WHEN EXPERIENCE = 'SENIOR' THEN TRUE
            END)
            SELECT ID, (SELECT * FROM CTE3), (SELECT * FROM CTE1), (SELECT * FROM CTE2)                  
$$; 

SELECT * FROM TABLE(new_employee_hired('123456789', 'DEVELOPER', 'SENIOR'));
   ID_EMPLOYEE|  WORKING_FROM_HOME|     TEAM|   COMPUTER|
--------------+-------------------|---------|-----------|
     123456789|                  1|   TEAM_1|     LAPTOP|

In case there are nested if statements and more than one variables are modified in the statements it is necessary to use a stored procedure.

Update values previously inserted

Updates columns values of the table into the function body and returns it with the new values.

SQL Server

IN -> SqlServer_04.sql
CREATE OR ALTER FUNCTION get_employees_history()
RETURNS @employee_history TABLE (
	department_name NVARCHAR(50),
	first_name NVARCHAR(50),
	last_name NVARCHAR(50), 
	start_date DATE,
	end_date DATE,
	job_title NVARCHAR(50), 
	months_working INT
)
BEGIN
	INSERT INTO @employee_history
	SELECT D.name AS department_name, P.first_name, P.last_name, EH.start_date, EH.end_date, E.job_title, 0 FROM Department D
	LEFT OUTER JOIN employee_department_history EH
		ON D.department_ID = EH.department_ID
	INNER JOIN  Employee E
		ON E.business_entity_ID = EH.business_entity_ID
	INNER JOIN Person P
		ON P.business_entity_ID = E.business_entity_ID 
		

	UPDATE @employee_history
	SET 
		months_working = 
		CASE WHEN end_date IS NULL THEN DATEDIFF(MONTH, start_date, GETDATE())
		ELSE DATEDIFF(MONTH, start_date, end_date)
	END
	RETURN;
END;

SELECT TOP(10) * FROM get_employees_history();
                      DEPARTMENT_NAME|         FIRST_NAME|          LAST_NAME|      START_DATE|         END_DATE|                           JOB_TITLE|        MONTHS_WORKING|
-------------------------------------+ ------------------+-------------------+----------------+-----------------+------------------------------------+----------------------+
                                Sales|               Syed|              Abbas|      2013-03-14| 	    NULL|               Pacific Sales Manager|                   106|
                           Production|                Kim|        Abercrombie|      2010-01-16|             NULL|        Production Technician - WC60|                   144|
                    Quality Assurance|              Hazem|           Abolrous|      2009-02-28|             NULL|           Quality Assurance Manager|                   155|
               Shipping and Receiving|              Pilar|           Ackerman|      2009-01-02|             NULL|   Shipping and Receiving Supervisor|                   156|
                           Production|                Jay|              Adams|      2009-03-05|             NULL|        Production Technician - WC60|                   154|
                 Information Services|           François|           Ajenstat|      2009-01-17|             NULL|              Database Administrator|                   156|
                                Sales|                Amy|            Alberts|      2012-04-16|             NULL|              European Sales Manager|                   117|
                           Production|               Greg|           Alderson|      2008-12-02|             NULL|        Production Technician - WC45|                   157|
                    Quality Assurance|               Sean|          Alexander|      2008-12-28|             NULL|        Quality Assurance Technician|                   157|
           Facilities and Maintenance|               Gary|             Altman|      2009-12-02|             NULL|                  Facilities Manager|                   145|

Snowflake SQL

OUT -> SqlServer_04.sql
CREATE OR REPLACE FUNCTION get_employees_history()
RETURNS TABLE(
    department_name VARCHAR(50),
    first_name VARCHAR(50),
    last_name VARCHAR(50),
    start_date DATE,
    end_date DATE,
    job_title VARCHAR(50),
    months_working INT)
AS
$$
    WITH CTE AS (
      SELECT D.name AS department_name, P.first_name, P.last_name, EH.start_date, EH.end_date, E.job_title, 
        CASE WHEN end_date IS NULL THEN DATEDIFF(MONTH, start_date, GETDATE()) 
        ELSE DATEDIFF(MONTH, start_date, end_date) END as months_working 
      FROM Department D
      LEFT OUTER JOIN employee_department_history  EH
          ON D.department_ID = EH.department_ID
      INNER JOIN  Employee E
          ON E.business_entity_ID = EH.business_entity_ID
      INNER JOIN Person P
          ON P.business_entity_ID = E.business_entity_ID
    )
    SELECT * FROM CTE
   
$$;

SELECT TOP 10 * FROM TABLE(get_employees_history());
                      DEPARTMENT_NAME|         FIRST_NAME|          LAST_NAME|      START_DATE|         END_DATE|                           JOB_TITLE|        MONTHS_WORKING|
-------------------------------------+ ------------------+-------------------+----------------+-----------------+------------------------------------+----------------------+
                                Sales|               Syed|              Abbas|      2013-03-14| 	    NULL|               Pacific Sales Manager|                   106|
                           Production|                Kim|        Abercrombie|      2010-01-16|             NULL|        Production Technician - WC60|                   144|
                    Quality Assurance|              Hazem|           Abolrous|      2009-02-28|             NULL|           Quality Assurance Manager|                   155|
               Shipping and Receiving|              Pilar|           Ackerman|      2009-01-02|             NULL|   Shipping and Receiving Supervisor|                   156|
                           Production|                Jay|              Adams|      2009-03-05|             NULL|        Production Technician - WC60|                   154|
                 Information Services|           François|           Ajenstat|      2009-01-17|             NULL|              Database Administrator|                   156|
                                Sales|                Amy|            Alberts|      2012-04-16|             NULL|              European Sales Manager|                   117|
                           Production|               Greg|           Alderson|      2008-12-02|             NULL|        Production Technician - WC45|                   157|
                    Quality Assurance|               Sean|          Alexander|      2008-12-28|             NULL|        Quality Assurance Technician|                   157|
           Facilities and Maintenance|               Gary|             Altman|      2009-12-02|             NULL|                  Facilities Manager|                   145|

Multiple return clauses

In the following sample there is more than one return clause, this is because depending on the situation it is not necessary to keep executing the whole function.

SQL Server

IN -> SqlServer_05.sql
CREATE OR ALTER FUNCTION create_new_team(@team_name VARCHAR(50))
RETURNS @new_team TABLE (type VARCHAR(50), name VARCHAR(50))
AS
BEGIN 
	DECLARE @employees INT
	SET @employees = (SELECT count(*) FROM employee)
	DECLARE @type VARCHAR(15)
	SET @type = 'small_team'
	IF (@employees < 8)
	BEGIN
		INSERT @new_team VALUES (@type, @team_name) 
		RETURN
	END
	
	SET @type = 'big_team'
	INSERT @new_team VALUES (@type, @team_name) 

	RETURN
END

SELECT * FROM create_new_team('Team1');             
          TYPE|           NAME|
--------------+ --------------+
    SMALL_TEAM|          TEAM1|
 

Snowflake SQL

OUT -> SqlServer_05.sql
CREATE OR REPLACE FUNCTION create_new_team(team_name VARCHAR(50))
RETURNS TABLE(type VARCHAR(50), name VARCHAR(50))
AS
$$   
     WITH CTE1 AS (SELECT COUNT(*) AS employees FROM EMPLOYEE),
     CTE2 AS (SELECT CASE WHEN(SELECT employees FROM cte1) < 8 THEN 'small_team' ELSE'big_team' END AS type)
     SELECT (SELECT type FROM CTE2), TEAM_NAME
$$;

SELECT * FROM TABLE(create_new_team('Team1'));
          TYPE|           NAME|
--------------+ --------------+
    SMALL_TEAM|          TEAM1|

This transformation is applied when there is only one value to insert, if there is more than one value it is necessary to use a stored procedure.

Complex cases

The example is a complex case that uses nested if statements and inserts a value depending on the true condition.

SQL Server

IN -> SqlServer_06.sql
CREATE OR ALTER FUNCTION vacation_status(@id VARCHAR (50))
RETURNS @status TABLE (vacation_status VARCHAR(30))
AS
BEGIN 
	DECLARE @hire_date DATETIME
	SET @hire_date = (SELECT @hire_date FROM employee WHERE employeeId = @id)
	DECLARE @vacation_hours INT
	SET @vacation_hours = (SELECT count(vacation_hours) FROM employee WHERE employeeId = @id)
	DECLARE @time_working INT
	SET @time_working = (SELECT DATEDIFF(MONTH, @hire_date,GETDATE()))

	IF (@vacation_hours > 0)
	BEGIN
		IF (@time_working > 3)
		BEGIN
			IF (@vacation_hours < 120)
			BEGIN
				INSERT INTO @status VALUES ('Ok')
			END

			IF (@vacation_hours = 120)
			BEGIN
				INSERT INTO @status values ('In the limit')
			END

			IF (@vacation_hours > 120)
			BEGIN
				INSERT INTO @status VALUES ('With excess')
			END
		END
		ELSE
		BEGIN
			INSERT INTO @status values ('Hired recently')
		END
	END
	ELSE
	BEGIN
		INSERT INTO @status values ('No hours')
	END
	RETURN
END

SELECT * FROM vacation_status('adventure-worksken0')
  VACATION_STATUS|
-----------------+
               OK|

Snowflake SQL

OUT -> SqlServer_06.sql
CREATE OR REPLACE FUNCTION vacation_status (ID VARCHAR(50))
RETURNS TABLE (vacation_status varchar(30))
AS
$$
     WITH CTE1 AS (SELECT hiredate AS hire_date FROM EMPLOYEE WHERE employeeId = ID),
     CTE2 AS (SELECT COUNT(vacationhours) AS vacation_hours FROM EMPLOYEE WHERE loginid = ID),
     CTE3 AS (SELECT DATEDIFF(MONTH, (SELECT * from CTE1), GETDATE()) AS time_working),
     CTE4 AS (SELECT CASE 
            WHEN (SELECT vacation_hours FROM CTE2) > 0 THEN (
                    SELECT CASE 
                        WHEN (SELECT time_working FROM CTE3) > 3 THEN (
                                SELECT CASE
                                  WHEN (SELECT vacation_hours FROM CTE2) < 120 THEN 'Ok'
                                  WHEN (SELECT vacation_hours FROM CTE2) = 120 THEN 'On the limit'
                                  WHEN (SELECT vacation_hours FROM CTE2) > 120 THEN 'With excess'                                                                                                                       
                                  END
                               )   
              ELSE 'Hired recently'
              END
                )                                                          
            ELSE 'No hours'
            END AS result) 
            SELECT result FROM CTE4
$$;

SELECT * FROM TABLE(vacation_status('adventure-worksken0'));
  VACATION_STATUS|
-----------------+
               OK|

Known Issues

While statements along side queries

The problem with this example is that there's no way of transforming the while statement to a CTE inside the WITH clause of the main select, this forces us to transform this statement to store procedure to maintain the same logic.

SQL Server

IN -> SqlServer_07.sql
CREATE OR ALTER FUNCTION get_group_name
(@department_id INT)
RETURNS @group_names TABLE (group_name VARCHAR(15))
AS
BEGIN
DECLARE @name VARCHAR(30) = 'Another Department'  
WHILE @name = 'Another Department'   
BEGIN    
	IF (@department_id < 3)
	BEGIN
		SET @name = 'engineering'
	END
	  
	IF @department_id = 3
	BEGIN
		SET @name = 'Tool Design'
	END
	
	SELECT @department_id = @department_id / 3
END
INSERT @group_names SELECT @name
RETURN
END

SELECT * FROM get_group_name(9);
        GROUP_NAME|
------------------+
       Tool Design|

Snowflake SQL

OUT -> SqlServer_07.sql
CREATE OR REPLACE PROCEDURE number_type(department_id FLOAT) 
RETURNS STRING
LANGUAGE JAVASCRIPT
EXECUTE AS CALLER 
AS 
$$ 
    // REGION SnowConvert Helpers Code
   var fetch = (count,rows,stmt) => (count && rows.next() && Array.apply(null,Array(stmt.getColumnCount())).map((_,i) => rows.getColumnValue(i + 1))) || [];
   var SELECT = (sql,binds = [],...args) => {
      var reducers = args.filter((i) => i instanceof Function);
      reducers = reducers.length ? reducers : [(value) => value]
      args = args.splice(0,args.length - reducers.length)
      EXEC("SELECT " + sql,binds)
      if (ROW_COUNT < 1) return;
      var colCount = _ROWS.getColumnCount();
      if (colCount != reducers.length) throw new Error("Missing arguments results has ${colCount} columns");
      var cols = Array.from(Array(colCount),() => []);
      while ( _ROWS.next() ) {
         for(var i = 0;i < colCount;i++) {
            cols[i].push(_ROWS.getColumnValue(i + 1))
         }
      }
      if (colCount == 1) {
         cols[0].forEach((value) => reducers[0](value))
         return (cols[0])[0];
      }
      for(var i = 0;i < colCount;i++) {
         cols[i].forEach((value) => reducers[i](value))
      }
   };
   var _RS, ROW_COUNT, _ROWS, MESSAGE_TEXT, SQLCODE = 0, SQLSTATE = '00000', OBJECT_SCHEMA_NAME  = 'UNKNOWN', ERROR_HANDLERS, NUM_ROWS_AFFECTED, PROC_NAME = arguments.callee.name, DOLLAR_DOLLAR = '$' + '$';
   function* sqlsplit(sql) {
      var part = '';
      var ismark = () => sql[i] == '$' && sql[i + 1] == '$';
      for(var i = 0;i < sql.length;i++) {
         if (sql[i] == ';') {
            yield part + sql[i];
            part = '';
         } else if (ismark()) {
            part += sql[i++] + sql[i++];
            while ( i < sql.length && !ismark() ) {
               part += sql[i++];
            }
            part += sql[i] + sql[i++];
         } else part += sql[i];
      }
      if (part.trim().length) yield part;
   };
   var formatDate = (arg) => (new Date(arg - (arg.getTimezoneOffset() * 60000))).toISOString().slice(0,-1);
   var fixBind = function (arg) {
      arg = arg == undefined ? null : arg instanceof Date ? formatDate(arg) : arg;
      return arg;
   };
   var EXEC = (stmt,binds = [],severity = "16",noCatch = false) => {
      binds = binds ? binds.map(fixBind) : binds;
      for(var stmt of sqlsplit(stmt)) {
         try {
            _RS = snowflake.createStatement({
                  sqlText : stmt,
                  binds : binds
               });
            _ROWS = _RS.execute();
            ROW_COUNT = _RS.getRowCount();
            NUM_ROWS_AFFECTED = _RS.getNumRowsAffected();
            return {
               THEN : (action) => !SQLCODE && action(fetch(_ROWS))
            };
         } catch(error) {
            let rStack = new RegExp('At .*, line (\\d+) position (\\d+)');
            let stackLine = error.stackTraceTxt.match(rStack) || [0,-1];
            MESSAGE_TEXT = error.message.toString();
            SQLCODE = error.code.toString();
            SQLSTATE = error.state.toString();
            snowflake.execute({
               sqlText : `SELECT UPDATE_ERROR_VARS_UDF(?,?,?,?,?,?)`,
               binds : [stackLine[1],SQLCODE,SQLSTATE,MESSAGE_TEXT,PROC_NAME,severity]
            });
            throw error;
         }
      }
   };
   // END REGION

   EXEC(`CREATE OR REPLACE TEMPORARY TABLE T_group_names(group_name VARCHAR(30));`);
   let NAME = `Another Department`;
   while ( NAME == `Another Department` ) {
      if (DEPARTMENT_ID < 3) {
         {
            NAME = `engineering`;
         }
      }
      if (DEPARTMENT_ID == 3) {
         {
            NAME = `Tool Design`;
         }
      }
      // ** MSC-INFORMATION - MSCINF0016 - SELECTION OF @department_id MAY HAVE RUNTIME ERRORS **
      SELECT(`? / 3`,[DEPARTMENT_ID],(value) => DEPARTMENT_ID = value);
   }
   EXEC(`INSERT INTO T_group_names SELECT ?`,[NAME]);
   EXEC(`SELECT * from T_group_names`);
   return NAME;
$$; 

CALL number_type(9);
SELECT * FROM T_group_names;
        GROUP_NAME|
------------------+
       Tool Design|

Declare Cursor

User-defined functions cannot DECLARE, OPEN, FETCH, CLOSE or DEALLOCATE a CURSOR. Use a Stored Procedure to work with cursors.

SQL Server

IN -> SqlServer_08.sql
CREATE OR ALTER FUNCTION amount_new_specimens(@id int)
RETURNS @new_specimens TABLE (amount int)
AS
BEGIN
	DECLARE @first_specimen VARCHAR(30) ;
	set @first_specimen = (select name_specimen from specimen where specimen_id = @id);
	DECLARE @second_specimen VARCHAR(30);

	DECLARE @specimens TABLE (name_specimen VARCHAR(30))

	DECLARE Cursor1 CURSOR 
	FOR SELECT name_specimen 
	FROM specimen 

	OPEN cursor1
	FETCH NEXT FROM cursor1
	INTO @second_specimen;
	
	WHILE @@FETCH_STATUS = 0 
	BEGIN
		IF @first_specimen <> @second_specimen
		BEGIN
			INSERT INTO @specimens values (CONCAT_WS('-', @first_specimen, @second_specimen))
		END
		FETCH NEXT FROM cursor1
		INTO @second_specimen;
	END

	CLOSE cursor1;
	DEALLOCATE cursor1;

	INSERT INTO @new_specimens SELECT COUNT(*) FROM @specimens
	RETURN 
END

SELECT * FROM amount_new_specimens(1);
            AMOUNT|
------------------+
                 3|

Snowflake SQL

OUT -> SqlServer_08.sql
CREATE OR REPLACE PROCEDURE amount_new_specimens(ID float)
RETURNS STRING
LANGUAGE JAVASCRIPT
EXECUTE AS CALLER
AS
$$
      // REGION SnowConvert Helpers Code
   var fetch = (count,rows,stmt) => (count && rows.next() && Array.apply(null,Array(stmt.getColumnCount())).map((_,i) => rows.getColumnValue(i + 1))) || [];
   var SELECT = (sql,binds = [],...args) => {
      var reducers = args.filter((i) => i instanceof Function);
      reducers = reducers.length ? reducers : [(value) => value]
      args = args.splice(0,args.length - reducers.length)
      EXEC("SELECT " + sql,binds)
      if (ROW_COUNT < 1) return;
      var colCount = _ROWS.getColumnCount();
      if (colCount != reducers.length) throw new Error("Missing arguments results has ${colCount} columns");
      var cols = Array.from(Array(colCount),() => []);
      while ( _ROWS.next() ) {
         for(var i = 0;i < colCount;i++) {
            cols[i].push(_ROWS.getColumnValue(i + 1))
         }
      }
      if (colCount == 1) {
         cols[0].forEach((value) => reducers[0](value))
         return (cols[0])[0];
      }
      for(var i = 0;i < colCount;i++) {
         cols[i].forEach((value) => reducers[i](value))
      }
   };
   var _RS, ROW_COUNT, _ROWS, MESSAGE_TEXT, SQLCODE = 0, SQLSTATE = '00000', OBJECT_SCHEMA_NAME  = 'UNKNOWN', ERROR_HANDLERS, NUM_ROWS_AFFECTED, PROC_NAME = arguments.callee.name, DOLLAR_DOLLAR = '$' + '$';
   function* sqlsplit(sql) {
      var part = '';
      var ismark = () => sql[i] == '$' && sql[i + 1] == '$';
      for(var i = 0;i < sql.length;i++) {
         if (sql[i] == ';') {
            yield part + sql[i];
            part = '';
         } else if (ismark()) {
            part += sql[i++] + sql[i++];
            while ( i < sql.length && !ismark() ) {
               part += sql[i++];
            }
            part += sql[i] + sql[i++];
         } else part += sql[i];
      }
      if (part.trim().length) yield part;
   };
   var formatDate = (arg) => (new Date(arg - (arg.getTimezoneOffset() * 60000))).toISOString().slice(0,-1);
   var fixBind = function (arg) {
      arg = arg == undefined ? null : arg instanceof Date ? formatDate(arg) : arg;
      return arg;
   };
   var EXEC = (stmt,binds = [],severity = "16",noCatch = false) => {
      binds = binds ? binds.map(fixBind) : binds;
      for(var stmt of sqlsplit(stmt)) {
         try {
            _RS = snowflake.createStatement({
                  sqlText : stmt,
                  binds : binds
               });
            _ROWS = _RS.execute();
            ROW_COUNT = _RS.getRowCount();
            NUM_ROWS_AFFECTED = _RS.getNumRowsAffected();
            return {
               THEN : (action) => !SQLCODE && action(fetch(_ROWS))
            };
         } catch(error) {
            let rStack = new RegExp('At .*, line (\\d+) position (\\d+)');
            let stackLine = error.stackTraceTxt.match(rStack) || [0,-1];
            MESSAGE_TEXT = error.message.toString();
            SQLCODE = error.code.toString();
            SQLSTATE = error.state.toString();
            snowflake.execute({
               sqlText : `SELECT UPDATE_ERROR_VARS_UDF(?,?,?,?,?,?)`,
               binds : [stackLine[1],SQLCODE,SQLSTATE,MESSAGE_TEXT,PROC_NAME,severity]
            });
            throw error;
         }
      }
   };
   var CURSOR = function (stmt,binds) {
      var statementObj, result_set, total_rows, isOpen = false, result_set_table = '', self = this;
      this.CURRENT = new Object;
      this.OPEN = function (usingParams) {
            try {
               if (usingParams) binds = usingParams;
               if (binds instanceof Function) binds = binds();
               var finalBinds = binds && binds.map(fixBind);
               var finalStmt = stmt instanceof Function ? stmt() : stmt;
               statementObj = snowflake.createStatement({
                     sqlText : finalStmt,
                     binds : finalBinds
                  });
               result_set = statementObj.execute();
               total_rows = statementObj.getRowCount();
               isOpen = true;
               row_count = 0;
            } catch(error) {
               RAISE(error.code,"error",error.message);
            }
            return this;
         };
      this.CURSOR_ROWS = function () {
            return total_rows;
         };
      this.FETCH_STATUS = function () {
            return total_rows >= row_count;
         };
      this.FETCH_NEXT = function () {
            self.res = [];
            self.res = fetch(total_rows,result_set,statementObj);
            if (self.res) row_count++
            return self.res && self.res.length > 0;
         };
      this.INTO = function () {
            return self.res;
         };
      this.CLOSE = function () {
            isOpen = row_count = result_set_table = total_rows = result_set = statementObj = undefined;
         };
      this.DEALLOCATE = function () {
            this.CURRENT = self = undefined;
         };
   };
   // END REGION

   EXEC(`CREATE OR REPLACE TEMPORARY TABLE T_new_specimens(amount int);`);
   let FIRST_SPECIMEN;
   FIRST_SPECIMEN = SELECT(`name_specimen from specimen where specimen_id = ?`,[ID]);
   let SECOND_SPECIMEN;
   EXEC(`CREATE OR REPLACE TEMPORARY TABLE T_specimens (name_specimen VARCHAR(30))`);
   var CURSOR1 = new CURSOR(`SELECT name_specimen FROM specimen`,[],false);
   CURSOR1.OPEN();
   CURSOR1.FETCH_NEXT() && ([SECOND_SPECIMEN] = CURSOR1.INTO());
   while ( CURSOR1.FETCH_STATUS() ) {
      if (FIRST_SPECIMEN != SECOND_SPECIMEN) {
         {
            EXEC(`INSERT INTO T_specimens values (CONCAT_WS('-', ?, ?))`,[FIRST_SPECIMEN,SECOND_SPECIMEN]);
         }
      }
      CURSOR1.FETCH_NEXT() && ([SECOND_SPECIMEN] = CURSOR1.INTO());
   }
   CURSOR1.CLOSE();
   CURSOR1.DEALLOCATE();
   EXEC(`INSERT INTO T_new_specimens SELECT COUNT(*) FROM T_specimens`);
   return;
$$;

CALL amount_new_specimens(1);
SELECT * FROM T_new_specimens;
            AMOUNT|
------------------+
                 3|

Different statements are not supported in Common Tables Expressions

The clauses UPDATE, INSERT, DELETE, ALTER or DROP are not supported on the body of common tables expressions, even after their declaration using a delimitator. For this reason, the function can be modified to work as a stored procedure.

SQL Server

IN -> SqlServer_09.sql
CREATE OR ALTER PROCEDURE product_history 
AS
BEGIN
	DECLARE @product_history TABLE (
		product_name NVARCHAR(50),
		rating INT
	)
	INSERT INTO @product_history
	SELECT P.Name AS product_name, AVG(ALL R.rating) FROM Production.product P
	INNER JOIN  Production.product_review R
		ON R.product_ID = P.product_ID
	GROUP BY P.Name;

	DELETE FROM @product_history
	WHERE rating < 2;

	SELECT * FROM @product_history; 

END
GO;

EXEC product_history
                      PRODUCT_NAME|            Rating|
----------------------------------+------------------|
                 HL Mountain Pedal|                 3|
            Mountain Bike Socks, M|                 5|
             Road-550-W Yellow, 40|                 5|

Snowflake SQL

OUT -> SqlServer_09.sql
CREATE OR REPLACE PROCEDURE product_history ()
RETURNS STRING
LANGUAGE JAVASCRIPT
COMMENT = '{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},{"attributes":{"component":"transact"}}'
EXECUTE AS CALLER
AS
$$
	// REGION SnowConvert Helpers Code
	var _RS, ROW_COUNT, _ROWS, MESSAGE_TEXT, SQLCODE = 0, SQLSTATE = '00000', OBJECT_SCHEMA_NAME  = 'UNKNOWN', ERROR_HANDLERS, NUM_ROWS_AFFECTED, PROC_NAME = arguments.callee.name, DOLLAR_DOLLAR = '$' + '$';
	function* sqlsplit(sql) {
		var part = '';
		var ismark = () => sql[i] == '$' && sql[i + 1] == '$';
		for(var i = 0;i < sql.length;i++) {
			if (sql[i] == ';') {
				yield part + sql[i];
				part = '';
			} else if (ismark()) {
				part += sql[i++] + sql[i++];
				while ( i < sql.length && !ismark() ) {
					part += sql[i++];
				}
				part += sql[i] + sql[i++];
			} else part += sql[i];
		}
		if (part.trim().length) yield part;
	};
	var formatDate = (arg) => (new Date(arg - (arg.getTimezoneOffset() * 60000))).toISOString().slice(0,-1);
	var fixBind = function (arg) {
		arg = arg == undefined ? null : arg instanceof Date ? formatDate(arg) : arg;
		return arg;
	};
	var EXEC = (stmt,binds = [],severity = "16",noCatch = false) => {
		binds = binds ? binds.map(fixBind) : binds;
		for(var stmt of sqlsplit(stmt)) {
			try {
				_RS = snowflake.createStatement({
						sqlText : stmt,
						binds : binds
					});
				_ROWS = _RS.execute();
				ROW_COUNT = _RS.getRowCount();
				NUM_ROWS_AFFECTED = _RS.getNumRowsAffected();
				return {
					THEN : (action) => !SQLCODE && action(fetch(_ROWS))
				};
			} catch(error) {
				let rStack = new RegExp('At .*, line (\\d+) position (\\d+)');
				let stackLine = error.stackTraceTxt.match(rStack) || [0,-1];
				MESSAGE_TEXT = error.message.toString();
				SQLCODE = error.code.toString();
				SQLSTATE = error.state.toString();
				snowflake.execute({
					sqlText : `SELECT UPDATE_ERROR_VARS_UDF(?,?,?,?,?,?)`,
					binds : [stackLine[1],SQLCODE,SQLSTATE,MESSAGE_TEXT,PROC_NAME,severity]
				});
				throw error;
			}
		}
	};
	// END REGION

	EXEC(`CREATE OR REPLACE TEMPORARY TABLE T_product_history (
   product_name VARCHAR(50),
   rating INT
	)`);
	EXEC(`	INSERT INTO T_product_history
	SELECT
	   P.Name AS product_name,
	   AVG(ALL R.rating) FROM
	   Production.product P
	   INNER JOIN
	      Production.product_review R
	      ON R.product_ID = P.product_ID
	GROUP BY
	   P.Name`);
	EXEC(`DELETE FROM
   T_product_history
   WHERE
   rating < 2`);
	EXEC(`
	SELECT
	   *
	FROM
	   T_product_history`);
$$;

!!!RESOLVE EWI!!! /*** SSC-EWI-0040 - THE STATEMENT IS NOT SUPPORTED IN SNOWFLAKE ***/!!!
;

CALL product_history();
                      PRODUCT_NAME|            Rating|
----------------------------------+------------------|
                 HL Mountain Pedal|                 3|
            Mountain Bike Socks, M|                 5|
             Road-550-W Yellow, 40|                 5|

Related EWIs

: User defined function was transformed to a Snowflake procedure.

INLINE TABLE-VALUED
SQL Server Language Reference Creating a multi-statement table-valued function
SSC-EWI-0068