Procedures
Translation reference to convert Teradata PROCEDURE statement to Snowflake
Description
Teradata's PROCEDURE statement is translated to Snowflake PROCEDURE syntax.
For more information on Teradata PROCEDURE, click here.
Sample Source Patterns
Create Procedure Transformation
SnowConvert procedure helpers region
All procedures contain a region called SnowConvert Helpers code, these are a set of variables and functions that help to emulate some Teradata functionality that is not native in JavaScript. For simplicity in the procedures samples code this region will not be shown. Some helpers are added always like EXEC and some others like FETCH, INTO, etc. are added on demand.
The region helpers code is as follows:
// REGION SnowConvert Helpers Code
var HANDLE_NOTFOUND;
var fetch = (count,rows,stmt) => (count && rows.next() && Array.apply(null,Array(stmt.getColumnCount())).map((_,i) => rows.getColumnValue(i + 1))) || [];
var _RS, ROW_COUNT, _ROWS, MESSAGE_TEXT, SQLCODE = 0, SQLSTATE = '00000', ERROR_HANDLERS, ACTIVITY_COUNT = 0, INTO, _OUTQUERIES = [], DYNAMIC_RESULTS = 9;
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 = function (stmt,binds,noCatch,catchFunction,opts) {
try {
binds = binds ? binds.map(fixBind) : binds;
_RS = snowflake.createStatement({
sqlText : stmt,
binds : binds
});
_ROWS = _RS.execute();
ROW_COUNT = _RS.getRowCount();
ACTIVITY_COUNT = _RS.getNumRowsAffected();
HANDLE_NOTFOUND && HANDLE_NOTFOUND(_RS);
if (INTO) return {
INTO : function () {
return INTO();
}
};
if (_OUTQUERIES.length < DYNAMIC_RESULTS) _OUTQUERIES.push(_ROWS.getQueryId());
if (opts && opts.temp) return _ROWS.getQueryId();
} catch(error) {
MESSAGE_TEXT = error.message;
SQLCODE = error.code;
SQLSTATE = error.state;
var msg = `ERROR CODE: ${SQLCODE} SQLSTATE: ${SQLSTATE} MESSAGE: ${MESSAGE_TEXT}`;
if (catchFunction) catchFunction(error);
if (!noCatch && ERROR_HANDLERS) ERROR_HANDLERS(error); else throw new Error(msg);
}
};
var CURSOR = function (stmt,binds,withReturn) {
var rs, rows, row_count, opened = false, resultsetTable = '', self = this;
this.CURRENT = new Object;
this.INTO = function () {
return self.res;
};
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;
if (withReturn) {
resultsetTable = EXEC(finalStmt,finalBinds,true,null,{
temp : true
});
finalStmt = `SELECT * FROM TABLE(RESULT_SCAN('${resultsetTable}'))`;
finalBinds = [];
}
rs = snowflake.createStatement({
sqlText : finalStmt,
binds : finalBinds
});
rows = rs.execute();
row_count = rs.getRowCount();
ACTIVITY_COUNT = rs.getRowCount();
opened = true;
return this;
} catch(error) {
ERROR_HANDLERS && ERROR_HANDLERS(error);
}
};
this.NEXT = function () {
if (row_count && rows.next()) {
this.CURRENT = new Object;
for(let i = 1;i <= rs.getColumnCount();i++) {
(this.CURRENT)[rs.getColumnName(i)] = rows.getColumnValue(i);
}
return true;
} else return false;
};
this.FETCH = function () {
self.res = [];
self.res = fetch(row_count,rows,rs);
if (opened) if (self.res.length > 0) {
SQLCODE = 0;
SQLSTATE = '00000';
} else {
SQLCODE = 7362;
SQLSTATE = '02000';
var fetchError = new Error('There are not rows in the response');
fetchError.code = SQLCODE;
fetchError.state = SQLSTATE;
if (ERROR_HANDLERS) ERROR_HANDLERS(fetchError);
} else {
SQLCODE = 7631;
SQLSTATE = '24501';
}
return self.res && self.res.length > 0;
};
this.CLOSE = function () {
if (withReturn && _OUTQUERIES.includes(resultsetTable)) {
_OUTQUERIES.splice(_OUTQUERIES.indexOf(resultsetTable),1);
}
rs = rows = row_count = undefined;
opened = false;
resultsetTable = '';
};
};
let PROCRESULTS = (...OUTPARAMS) => JSON.stringify([...OUTPARAMS,[..._OUTQUERIES]]);
// END REGION
Teradata
REPLACE PROCEDURE my_procedure (in param1 VARCHAR(10), OUT param2 BLOB)
dynamic result sets 9
SELECT * FROM table1;
Snowflake
CREATE OR REPLACE PROCEDURE PUBLIC.my_procedure (PARAM1 STRING, PARAM2 BINARY)
RETURNS STRING
LANGUAGE JAVASCRIPT
EXECUTE AS CALLER
AS
$$
// REGION SnowConvert Helpers Code
EXEC(`SELECT * FROM PUBLIC.table1`,[]);
RETURN PROCRESULTS(PARAM2);
$$
Note: The stored procedure's body in Snowflake is executed as javascript functions.
If
The transformation for the IF statement is:
Teradata
IF value = 2 THEN
Snowflake
if(value == 2){
}
Case
The transformation for the Case statement is:
Teradata
case value
when 0 then
select * from table1
else
update table1 set name = "SpecificValue" where id = value;
end case
Snowflake
switch(value) {
case 0:EXEC(`SELECT * FROM PUBLIC.table1`,[]);
break;
default:EXEC(`UPDATE PUBLIC.table1 set name = "SpecificValue" where id = value`,[]);
break;
}
Cursor Declare, OPEN, FETCH and CLOSE
The transformation for cursor statements is:
Teradata
CREATE PROCEDURE procedure1()
DYNAMIC RESULT SETS 2
BEGIN
-------- Local variables --------
DECLARE sql_cmd VARCHAR(20000) DEFAULT ' ';
DECLARE num_cols INTEGER;
------- Declare cursor with return only-------
DECLARE resultset CURSOR WITH RETURN ONLY FOR firststatement;
------- Declare cursor -------
DECLARE cur2 CURSOR FOR SELECT COUNT(columnname) FROM table1;
-------- Set --------
SET sql_cmd='sel * from table1';
-------- Prepare cursor --------
PREPARE firststatement FROM sql_cmd;
-------- Open cursors --------
OPEN resultset;
OPEN cur1;
-------- Fetch -------------
FETCH cur1 INTO val1, val2;
-------- Close cursor --------
CLOSE cur1;
END;
Snowflake
CREATE OR REPLACE PROCEDURE PUBLIC.procedure1 ()
RETURNS STRING
LANGUAGE JAVASCRIPT
EXECUTE AS CALLER
AS
$$
// REGION SnowConvert Helpers Code
//------ Local variables --------
var SQL_CMD = ` `;
var NUM_COLS;
var RESULTSET = new CURSOR(() => FIRSTSTATEMENT,[],true);
//----- Declare cursor -------
var CUR2 = new CURSOR(`SELECT
COUNT(columnname)
FROM PUBLIC.table1`,[],false);
//------ Set --------
SQL_CMD = `SELECT
*
FROM PUBLIC.table1`;
//------ Prepare cursor --------
var FIRSTSTATEMENT = SQL_CMD;
//------ Open cursors --------
RESULTSET.OPEN();
CUR1.OPEN();
//------ Fetch -------------
CUR1.FETCH() && ([val1,val2] = CUR1.INTO());
//------ Close cursor --------
CUR1.CLOSE();
return PROCRESULTS();
$$;a
While
The transformation for while statement is:
Teradata
while (counter < 10) do
set counter = counter + 1;
Snowflake
while ( counter < 10) {
counter = counter + 1;
}
Security
The transformation for security statements is:
Teradata
Snowflake
SQL SECURITY CREATOR
EXECUTE AS OWNER
SQL SECURITY INVOKER
EXECUTE AS CALLER
SQL SECURITY DEFINER
EXECUTE AS OWNER
FOR-CURSOR-FOR loop
The transformation for FOR-CURSOR-FOR loop is:
Teradata
REPLACE PROCEDURE Database1.Proc1()
BEGIN
DECLARE lNumber INTEGER DEFAULT 1;
FOR class1 AS class2 CURSOR FOR
SELECT COL0,
TRIM(COL1) AS COL1ALIAS,
TRIM(COL2),
COL3
FROM someDb.prefixCol
DO
INSERT INTO TempDB.Table1 (:lgNumber, :lNumber, (',' || :class1.ClassCD || '_Ind CHAR(1) NOT NULL'));
SET lNumber = lNumber + 1;
END FOR;
END;
Snowflake
CREATE OR REPLACE PROCEDURE Database1.PUBLIC.Proc1 ()
RETURNS STRING
LANGUAGE JAVASCRIPT
EXECUTE AS CALLER
AS
$$
// REGION SnowConvert Helpers Code
var LNUMBER = 1;
//** MSC-WARNING - MSCEWI1023 - PERFORMANCE REVIEW - THIS LOOP CONTAINS AN INSERT, DELETE OR UPDATE STATEMENT **
for(var CLASS2 = new CURSOR(`SELECT
COL0,
TRIM(COL1) AS COL1ALIAS,
TRIM(COL2),
COL3
FROM someDb.PUBLIC.prefixCol`,[],false).OPEN();CLASS2.NEXT();) {
let CLASS1 = CLASS2.CURRENT;
EXEC(`INSERT INTO TempDB.PUBLIC.Table1 VALUES (:lgNumber, :1, (',' || :/*** MSC-WARNING - MSCEWI1026 - THE VARIABLE class1.ClassCD MAY REQUIRE A CAST TO DATE, TIME OR TIMESTAMP ***/
:2 || '_Ind CHAR(1) NOT NULL'))`,[LNUMBER,CLASS1.CLASSCD]);
LNUMBER = LNUMBER + 1;
}
CLASS2.CLOSE();
$$;
Note: The FOR loop present in the Teradata procedure is transformed to a FOR block in javascript that emulates its functionality.
Procedure parameters and variables referenced inside statements
The transformation for the procedure parameters and variables that are referenced inside the statements of the procedure is:
Teradata
REPLACE PROCEDURE PROC1 (param1 INTEGER, param2 VARCHAR(30))
BEGIN
DECLARE var1 VARCHAR(1024);
DECLARE var2 SMALLINT;
DECLARE weekstart date;
set weekstart= '2019-03-03';
set var1 = 'something';
set var2 = 123;
SELECT * FROM TABLE1 WHERE SOMETHING = :param1;
SELECT * FROM TABLE1 WHERE var1 = var1 AND date1 = weekstart AND param2 = :param2;
INSERT INTO TABLE2 (col1, col2, col3, col4, col5) VALUES (:param1, :param2, var1, var2, weekstart);
END;
Snowflake
CREATE OR REPLACE PROCEDURE sfSchema.PROC1 (param1 FLOAT, param2 STRING)
RETURNS STRING
LANGUAGE JAVASCRIPT
EXECUTE AS CALLER
AS
$$
// REGION SnowConvert Helpers Code
var VAR1;
var VAR2;
var WEEKSTART;
WEEKSTART = `2019-03-03`;
VAR1 = `something`;
VAR2 = 123;
// ** MSC-WARNING - MSCEWI1022 - ONE OR MORE IDENTIFIERS IN THIS STATEMENT WERE CONSIDERED PARAMETERS BY DEFAULT. REFERENCED TABLE NOT FOUND. **
EXEC(`SELECT * FROM PUBLIC.TABLE1 WHERE SOMETHING = :1`,[PARAM1]);
// ** MSC-WARNING - MSCEWI1022 - ONE OR MORE IDENTIFIERS IN THIS STATEMENT WERE CONSIDERED PARAMETERS BY DEFAULT. REFERENCED TABLE NOT FOUND. **
EXEC(`SELECT * FROM PUBLIC.TABLE1 WHERE :1 = :2 AND date1 = :3 AND param2 = :4`,[VAR1,VAR1,WEEKSTART,PARAM2]);
// ** MSC-WARNING - MSCEWI1022 - ONE OR MORE IDENTIFIERS IN THIS STATEMENT WERE CONSIDERED PARAMETERS BY DEFAULT. REFERENCED TABLE NOT FOUND. **
EXEC(`INSERT INTO PUBLIC.TABLE2 (col1, col2, col3, col4, col5) VALUES (:1, :2, :3, :4, :5)`,[PARAM1,PARAM2,VAR1,VAR2,WEEKSTART]);
$$;
Note: Whenever a procedure parameter or a variable declared inside the procedure is referenced inside a Teradata statement that has to be converted, this reference is escaped from the resulting text to preserve the original reference's functionality.
Leave
In Javascript, it's possible to use break
with an additional parameter, thus emulating the behavior of a Teradata LEAVE
jump.
Labels can also be emulated by using Javascript Labeled Statements.
The transformation for LEAVE statement is:
Teradata
REPLACE PROCEDURE PROC1 ()
BEGIN
DECLARE v_propval VARCHAR(1024);
DECLARE Cur1 cursor for
Select
propID
from viewName.viewCol
where propval is not null;
LABEL_WHILE:
WHILE (SQLCODE = 0)
DO
IF (SQLSTATE = '02000' )
THEN LEAVE LABEL_WHILE;
END IF;
LABEL_INNER_WHILE:
WHILE (SQLCODE = 0)
DO
IF (SQLSTATE = '02000' )
THEN LEAVE LABEL_INNER_WHILE;
END IF;
END WHILE LABEL_INNER_WHILE;
SELECT * FROM TABLE1;
END WHILE L1;
END;
Snowflake
CREATE OR REPLACE PROCEDURE PUBLIC.PROC1 ()
RETURNS STRING
LANGUAGE JAVASCRIPT
EXECUTE AS CALLER
AS
$$
var v_propval;
var CUR1 = new CURSOR(`SELECT
propID
FROM viewName.PUBLIC.viewCol
where propval is not null`,[],false);
L1:while ( SQLCODE == 0 ) {
if (SQLSTATE == `02000`) {
break LABEL_WHILE;
}
LABEL_INNER_WHILE:while ( SQLCODE == 0 ) {
if (SQLSTATE == `02000`) {
break LABEL_INNER_WHILE;
}
}
EXEC(`SELECT * FROM PUBLIC.TABLE1`,[]);
}
$$;
Getting Results from Procedures
Description of the translation
In Teradata, there are two ways to return data from a procedure. The first is through output parameters and the second through Dynamic Result Sets and Cursors. Both are shown in the following example. Each important point is explained below.
Example of returning data from a Stored Procedure
Teradata
REPLACE PROCEDURE Procedure1(OUT P1 INTEGER)
DYNAMIC RESULT SETS 2
BEGIN
DECLARE SQL_CMD,SQL_CMD_1 VARCHAR(20000) DEFAULT ' ';
DECLARE RESULTSET CURSOR WITH RETURN ONLY FOR FIRSTSTATEMENT;
SET SQL_CMD = 'SEL * FROM EMPLOYEE';
PREPARE FIRSTSTATEMENT FROM SQL_CMD;
OPEN RESULTSET;
SET P1 = (SEL CAST(AVG(AGE) AS INTEGER) FROM EMPLOYEE);
END;
Snowflake
CREATE OR REPLACE PROCEDURE PUBLIC.Procedure1 (P1 FLOAT)
RETURNS STRING
LANGUAGE JAVASCRIPT
EXECUTE AS CALLER
AS
$$
// REGION SnowConvert Helpers Code
var ..., _OUTQUERIES = [], DYNAMIC_RESULTS = 2;
// More helpers here
let PROCRESULTS = (...OUTPARAMS) => JSON.stringify([...OUTPARAMS,[..._OUTQUERIES]]);
// END REGION
var SQL_CMD = ` `;
var SQL_CMD_1 = ` `;
var RESULTSET = new CURSOR(() => FIRSTSTATEMENT,[],true);
SQL_CMD = `SELECT
*
FROM PUBLIC.EMPLOYEE`;
var FIRSTSTATEMENT = SQL_CMD;
RESULTSET.OPEN();
EXEC(`(SELECT
CAST(TRUNC(AVG(AGE)) AS int)
FROM PUBLIC.EMPLOYEE)`,[]);
var subQueryVariable0 = res.getColumnValue(1);
P1 = subQueryVariable0;
return PROCRESULTS(P1);
$$;
In this converted SQL, there are several conversions that take place:
The
DYNAMIC RESULT SETS 2
definition is converted to aDYNAMIC_RESULTS
variable.var DYNAMIC_RESULTS = 2;
When a cursor with an
WITH RETURN
attribute is opened (and therefore a query is executed), its query ID is stored in the_OUTQUERIES
collection in order to be later returned. The query id is obtained by thegetQueryId()
function provided in the JavaScript API for Snowflake stored procedures.Only the first k-query-IDs are stored in the collection, where k is the value of the
DYNAMIC_RESULTS
variable. This is done to emulate Teradata's behavior, which only returns the first k-opened-cursors, even if more are opened in the stored procedure.The combination of
DECLARE CURSOR WITH RETURN
withPREPARE
is translated to:var RESULTSET = new CURSOR(() => FIRSTSTATEMENT,[],true);
The output parameters are supported via the return statement of the procedure. An array is created containing the value of each output parameter and the
_OUTQUERIES
collection. ThePROCRESULTS
function deals with the creation and filling of this array. See PROCRESULTS() helper for more information.return PROCRESULTS(P1);
Example of getting data from a Stored Procedure
If the output parameters and the query IDs are returned from a procedure, a second one could call the first one to get these values, as shown below:
Teradata
CREATE PROCEDURE Procedure2()
BEGIN
DECLARE x INTEGER;
CALL Procedure1(x);
END;
Snowflake
CREATE OR REPLACE PROCEDURE PUBLIC.Procedure2 ()
RETURNS STRING
LANGUAGE JAVASCRIPT
EXECUTE AS CALLER
AS
$$
// REGION SnowConvert Helpers Code
var X;
EXEC(`CALL PUBLIC.spSample2(?)`,[X]);
!SQLCODE && ([X,resultset] = JSON.parse(INTO()));
$$;
The value of the
P1
argument fromProcedure1
is returned and stored in theX
variable.The
_OUTQUERIES
returned fromProcedure1
are stored in theresultset
variable.
Known Issues
No issues were found.
Related EWIs
MSCEWI1023 - PERFORMANCE REVIEW - THIS LOOP CONTAINS AN INSERT, DELETE OR UPDATE STATEMENT.
Last updated
Was this helpful?