PL/SQL lets you define two kinds of composite data types: collection and record, where composite is a data type that stores values that have internal components.
In a collection, the internal components always have the same data type, and are called elements.
Snowflake doesn’t support user-defined data types, which includes PL Collections and Records, according to its online documentation Unsupported Data Types, but it supports Semi-structured Data Types, which can be used to mimic both the hierarchy-like structure of Record and the element structure of Collection User-defined types. For this reason, there are multiple types of features that have no workaround.
Following are the features for which NO workaround is proposed:
Snowflake sets VARIANT, OBJECT, and ARRAY’s maximum size on 16MBs. This means that if a Record, a Collection, or any element of either exceeds this size it will cause a Runtime Error.
The next table shows a summary of the current support provided by the SnowConvert AI tool. Please keep in mind that translations may still not be final, and more work may be needed.
1. Associate Arrays are considered a Nested Table¶
As of now, SnowConvert AI doesn’t differentiate between an Associative Array and a Nested Table meaning they are mixed up in the same assessment counts.
An associative array (formerly called PL/SQL table or index-by table) is a set of key-value pairs. Each key is a unique index, used to locate the associated value with the syntax variable_name(index).
The data type of index can be either a string type (VARCHAR2, VARCHAR, STRING, or LONG) or PLS_INTEGER. Indexes are stored in sort order, not creation order. For string types, sort order is determined by the initialization parameters NLS_SORT and NLS_COMP.
For the translation, the type definition is replaced by an OBJECT Semi-structured Data Type and then its usages are changed accordingly across any operations.
To define an Associative Array type, the syntax is as follows:
Please note that the numeric value is converted to varchar accordingly when the operation needs it. Additionally, note the ‘true’ parameter in the OBJECT_INSERT. This is so that the element is updated if it is already present in the array.
CREATEORREPLACEPROCEDUREPUBLIC.numeric_associative_array ()RETURNSVARCHARLANGUAGESQLEXECUTEASCALLERAS$$
DECLARE
associate_array OBJECT := OBJECT_CONSTRUCT();
associate_index NUMBER;
BEGIN
associate_array := OBJECT_INSERT(associate_array, '1', -1, true);
associate_array := OBJECT_INSERT(associate_array, '2', -2, true);
associate_array := OBJECT_INSERT(associate_array, '3', -3, true);
CALL DBMS_OUTPUT.PUT_LINE(:associate_array['1']);
associate_array := OBJECT_INSERT(:associate_array, '1', -4, true);
CALL DBMS_OUTPUT.PUT_LINE(ARRAY_SIZE(OBJECT_KEYS(:associate_array)));
FOR i IN 1 TO ARRAY_SIZE(OBJECT_KEYS(:associate_array))
LOOP
associate_index := OBJECT_KEYS(:associate_array)[:i-1];
CALL DBMS_OUTPUT.PUT_LINE(:associate_array[:associate_index::VARCHAR]);
END LOOP;
END;
$$;CALLPUBLIC.numeric_associative_array();SELECT*FROM DBMS_OUTPUT.DBMS_OUTPUT_LOG;
In this case, the associative array is composed of a Record-structure, and this structure needs to be preserved. For this purpose, further operations on insertions were added.
In this scenario, the insertion/update assumes an automatic creation of the record within the associative array and this needs to be taken into account when creating new records.
A collection method is a PL/SQL subprogram—either a function that returns information about a collection or a procedure that operates on a collection. Collection methods make collections easier to use and your applications easier to maintain.
Some of these methods can be mapped to native Snowflake semi-structured operations. The ones that can’t or have differences will be mapped to a UDF implementation.
The next table shows a summary of the current support provided by the SnowConvert AI tool. Please keep in mind that translations may still not be final, and more work may be needed.
This method returns the count of “non-undefined” (not to be confused with null) elements within a collection (nested tables can become sparse leaving these elements in between). In associative arrays, it returns the number of keys in the array.
The snowflake equivalent is the ARRAY_CONTAINS method. Note that, when using Varchar elements, casting to Variant is necessary.
CREATEORREPLACEPROCEDUREPUBLIC.collection_exists()RETURNSVARCHARLANGUAGESQLEXECUTEASCALLERAS$$
DECLARE
associative_array OBJECT := OBJECT_CONSTRUCT('abc', 1, 'bca', 1);
nt_variable ARRAY := ARRAY_CONSTRUCT(1, 2, 3, 4);
BEGIN
IF (ARRAY_CONTAINS('abc'::VARIANT, OBJECT_KEYS(associative_array)))
THEN CALL DBMS_OUTPUT.PUT_LINE('Found');
END IF;
IF (NOT ARRAY_CONTAINS('not found'::VARIANT, OBJECT_KEYS(associative_array)))
THEN CALL DBMS_OUTPUT.PUT_LINE('Not found');
END IF;
IF (ARRAY_CONTAINS(1, nt_variable))
THEN CALL DBMS_OUTPUT.PUT_LINE('Found');
END IF;
IF (NOT ARRAY_CONTAINS(5, nt_variable))
THEN CALL DBMS_OUTPUT.PUT_LINE('Not found');
END IF;
END;
$$;CALLPUBLIC.collection_exists();SELECT*FROM DBMS_OUTPUT.DBMS_OUTPUT_LOG;
These two methods return the First/Last element of the collection, respectively. If the collection is empty it returns null. This operation is mapped to a UDF, which will be added in further revisions.
Snowflake does not support deletions from an existing ARRAY and for this reason, the only offered workaround is to rebuild a new ARRAY depending on the original parameters of the DELETE.
Note
Note that a UDF was added to implement the functionality for the update of the element.
This method returns the prior/next index, given an index. If there is not a prior/next then it returns null. It is most frequently used to traverse a collection.
In the database, a nested table is a column type that stores an unspecified number of rows in no particular order.
When you retrieve a nested table value from the database into a PL/SQL nested table variable, PL/SQL gives the rows consecutive indexes, starting at 1. Using these indexes, you can access the individual rows of the nested table variable. The syntax is variable_name(index). The indexes and row order of a nested table might not remain stable as you store and retrieve the nested table from the database.
For the translation, the type definition is replaced by an ARRAY Semi-structured Data Type and then its usages are changed accordingly across any operations. Please note how the translation for Nested Tables and Varrays are the same.
To define a Nested Table Array type, the syntax is as follows:
CREATEORREPLACEPROCEDURE nested_table_procedure()RETURNSINTEGERLANGUAGESQLEXECUTEASCALLERAS$$
DECLARE
-- NO LONGER NEEDED
/*
TYPE associative_array_typ IS TABLE OF INTEGER INDEX BY VARCHAR2(30);
TYPE associative_array_typ2 IS TABLE OF INTEGER INDEX BY PLS_INTEGER;
*/
associative_array ARRAY;
associative_array2 ARRAY;
BEGIN
NULL;
END;
$$;
Note that a UDF was added to implement the functionality for the update of the element.
This UDF will be added in later revisions.
CREATEORREPLACEPROCEDUREPUBLIC.nested_table_iteration()RETURNSVARCHARLANGUAGESQLEXECUTEASCALLERAS$$
DECLARE
nested_table_variable ARRAY := ARRAY_CONSTRUCT(10, 20, 30);
BEGIN
FOR i IN 1 TO ARRAY_SIZE(nested_table_variable)
LOOP
CALL DBMS_OUTPUT.PUT_LINE(:nested_table_variable[:i-1]);
END LOOP;
nested_table_variable:= INSERT_REPLACE_COLLECTION_ELEMENT_UDF(nested_table_variable, 1, 40);
FOR i IN 1 TO ARRAY_SIZE(nested_table_variable)
LOOP
CALL DBMS_OUTPUT.PUT_LINE(:nested_table_variable[:i-1]);
END LOOP;
END;
$$;CALLPUBLIC.nested_table_iteration();SELECT*FROM DBMS_OUTPUT.DBMS_OUTPUT_LOG;
A record variable is a composite variable whose internal components, called fields, can have different data types. The value of a record variable and the values of its fields can change.
You reference an entire record variable by its name. You reference a record field with the syntax record.field.
You can create a record variable in any of these ways:
Define a record type and then declare a variable of that type.
Use %ROWTYPE to declare a record variable that represents either a full or partial row of a database table or view.
Use %TYPE to declare a record variable of the same type as a previously declared record variable.
For the translation, the type definition is replaced by an OBJECT Semi-structured Data Type and then its usages are changed accordingly across any operations.
To define a Record type, the syntax is as follows:
Notice how the reassignments are replaced by an OBJECT_INSERT that updates if the column already exists, and how the VALUES clause is replaced by a SELECT.
CREATEORREPLACETABLE record_table (col1 FLOAT,
col2 INTEGER)COMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},{"attributes":{"component":"oracle"}}';CREATEORREPLACEPROCEDURE record_procedure ()RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},{"attributes":{"component":"oracle"}}'EXECUTEASCALLERAS$$
DECLARE
!!!RESOLVE EWI!!! /*** SSC-EWI-0056 - CUSTOM TYPES ARE NOT SUPPORTED IN SNOWFLAKE BUT REFERENCES TO THIS CUSTOM TYPE WERE CHANGED TO OBJECT ***/!!!
TYPE record_typ IS RECORD(col1 INTEGER, col2 FLOAT);
record_variable OBJECT !!!RESOLVE EWI!!! /*** SSC-EWI-0036 - record_typ DATA TYPE CONVERTED TO OBJECT ***/!!! := OBJECT_CONSTRUCT('COL1', 1, 'COL2', 1.5);--initialization
BEGIN
INSERT INTO record_table(col1, col2)
SELECT
:record_variable:COL2,
:record_variable:COL1;--usage
--reassignment of properties
record_variable := OBJECT_INSERT(record_variable, 'COL1', 2, true);
record_variable := OBJECT_INSERT(record_variable, 'COL2', 2.5, true);
INSERT INTO record_table(col1, col2)
SELECT
:record_variable:COL2,
:record_variable:COL1;--usage
END;
$$;CALL record_procedure();SELECT*FROM
record_table;
Since the operations are the ones that define the structure, these definitions can be replaced by an OBJECT datatype, but the values of the record need to be decomposed as inserting the record “as-is” is not supported.
Please note the additional OBJECT_CONSTRUCT in the Cursor definition, this is what allows to extract an OBJECT, which then can be used to seamlessly migrate the FETCH statement.
CREATEORREPLACETABLE record_table (col1 INTEGER,
col2 VARCHAR(50),
col3 TIMESTAMP/*** SSC-FDM-OR0042 - DATE TYPE COLUMN HAS A DIFFERENT BEHAVIOR IN SNOWFLAKE. ***/)COMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},"attributes":{"component":"oracle"}}';INSERTINTO record_table(col1, col2 , col3)VALUES(1,'Hello',DATE'2020-12-25');CREATEORREPLACEPROCEDURE load_cursor_record ()RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},"attributes":{"component":"oracle"}}'EXECUTEASCALLERAS$$
DECLARE
--** SSC-PRF-0009 - PERFORMANCE REVIEW - CURSOR USAGE **
record_cursor CURSOR
FOR
SELECT
OBJECT_CONSTRUCT( *) sc_cursor_record
FROM
record_table;
record_variable OBJECT !!!RESOLVE EWI!!! /*** SSC-EWI-0036 - ROWTYPE DATA TYPE CONVERTED TO OBJECT ***/!!! := OBJECT_CONSTRUCT();
BEGIN
OPEN record_cursor;
--** SSC-PRF-0008 - PERFORMANCE REVIEW - LOOP USAGE **
LOOP
--** SSC-PRF-0003 - FETCH INSIDE A LOOP IS CONSIDERED A COMPLEX PATTERN, THIS COULD DEGRADE SNOWFLAKE PERFORMANCE. **
FETCH record_cursor INTO
:record_variable;
IF (record_variable IS NULL) THEN
EXIT;
END IF;
--** SSC-FDM-OR0035 - CHECK UDF IMPLEMENTATION FOR DBMS_OUTPUT.PUT_LINE_UDF. **
CALL DBMS_OUTPUT.PUT_LINE_UDF(:record_variable:COL1);
--** SSC-FDM-OR0035 - CHECK UDF IMPLEMENTATION FOR DBMS_OUTPUT.PUT_LINE_UDF. **
CALL DBMS_OUTPUT.PUT_LINE_UDF(:record_variable:COL2);
--** SSC-FDM-OR0035 - CHECK UDF IMPLEMENTATION FOR DBMS_OUTPUT.PUT_LINE_UDF. **
CALL DBMS_OUTPUT.PUT_LINE_UDF(:record_variable:COL3::DATE);
END LOOP;
CLOSE record_cursor;
END;
$$;CALL load_cursor_record();
CREATEORREPLACEPROCEDURE sp_sample1 ()RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},{"attributes":{"component":"oracle"}}'EXECUTEASCALLERAS$$
DECLARE
-- Rowtype variable
rowtype_variable OBJECT !!!RESOLVE EWI!!! /*** SSC-EWI-0036 - ROWTYPE DATA TYPE CONVERTED TO OBJECT ***/!!! := OBJECT_CONSTRUCT();
--Record variable
!!!RESOLVE EWI!!! /*** SSC-EWI-0056 - CUSTOM TYPES ARE NOT SUPPORTED IN SNOWFLAKE BUT REFERENCES TO THIS CUSTOM TYPE WERE CHANGED TO OBJECT ***/!!!
TYPE record_typ_def IS RECORD(ID number, NAME varchar2(23));
record_variable_def OBJECT !!!RESOLVE EWI!!! /*** SSC-EWI-0036 - record_typ_def DATA TYPE CONVERTED TO OBJECT ***/!!! := OBJECT_CONSTRUCT();
-- Auxiliary variable
name_var VARCHAR(20);
BEGIN
SELECT
OBJECT_CONSTRUCT( *) INTO
:rowtype_variable
FROM
sample_table
WHERE ID = 1
FETCH NEXT 1 ROWS ONLY;
name_var := :rowtype_variable:NAME;
INSERT INTO RESULTS(COL1, COL2) VALUES('SELECT 1', :name_var);
SELECT
OBJECT_CONSTRUCT()
INTO
:rowtype_variable
FROM
sample_table
WHERE ID = 2
FETCH NEXT 1 ROWS ONLY;
name_var := :rowtype_variable:NAME;
INSERT INTO RESULTS(COL1, COL2) VALUES('SELECT 2', :name_var);
SELECT
OBJECT_CONSTRUCT( *) INTO
:record_variable_def
FROM
sample_table
WHERE ID = 3
FETCH NEXT 1 ROWS ONLY;
name_var := :record_variable_def:NAME;
INSERT INTO RESULTS(COL1, COL2) VALUES('SELECT 3', :name_var);
SELECT
OBJECT_CONSTRUCT('ID', ID, 'NAME', NAME) INTO
:record_variable_def
FROM
sample_table
WHERE ID = 4
FETCH NEXT 1 ROWS ONLY;
name_var := :record_variable_def:NAME;
INSERT INTO RESULTS(COL1, COL2) VALUES('SELECT 4', :name_var);
END;
$$;call sp_sample1();SELECT*FROM
results;
A varray (variable-size array) is an array whose number of elements can vary from zero (empty) to the declared maximum size.
To access an element of a varray variable, use the syntax variable_name(index). The lower bound of index is 1; the upper bound is the current number of elements. The upper bound changes as you add or delete elements, but it cannot exceed the maximum size. When you store and retrieve a varray from the database, its indexes and element order remain stable.
For the translation, the type definition is replaced by an ARRAY Semi-structured Data Type and then its usages are changed accordingly across any operations. Please note how the translation for Nested Tables and Varrays are the same.
To define a varray type, the syntax is as follows:
CREATEORREPLACEPROCEDURE associative_array_procedure ()RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},"attributes":{"component":"oracle"}}'EXECUTEASCALLERAS$$
DECLARE
-- !!!RESOLVE EWI!!! /*** SSC-EWI-0058 - FUNCTIONALITY FOR 'PL COLLECTION TYPE DEFINITION' IS NOT CURRENTLY SUPPORTED BY SNOWFLAKE SCRIPTING ***/!!!
-- TYPE varray_typ IS ARRAY(10) OF INTEGER;
-- !!!RESOLVE EWI!!! /*** SSC-EWI-0058 - FUNCTIONALITY FOR 'PL COLLECTION TYPE DEFINITION' IS NOT CURRENTLY SUPPORTED BY SNOWFLAKE SCRIPTING ***/!!!
-- TYPE varray_typ2 IS VARRAY(10) OF INTEGER;
-- !!!RESOLVE EWI!!! /*** SSC-EWI-0058 - FUNCTIONALITY FOR 'PL COLLECTION TYPE DEFINITION' IS NOT CURRENTLY SUPPORTED BY SNOWFLAKE SCRIPTING ***/!!!
-- TYPE varray_typ3 IS VARYING ARRAY(10) OF INTEGER;
array_variable VARIANT !!!RESOLVE EWI!!! /*** SSC-EWI-0062 - CUSTOM TYPE 'varray_typ' USAGE CHANGED TO VARIANT ***/!!!;
array_variable2 VARIANT !!!RESOLVE EWI!!! /*** SSC-EWI-0062 - CUSTOM TYPE 'varray_typ2' USAGE CHANGED TO VARIANT ***/!!!;
array_variable3 VARIANT !!!RESOLVE EWI!!! /*** SSC-EWI-0062 - CUSTOM TYPE 'varray_typ3' USAGE CHANGED TO VARIANT ***/!!!;
BEGIN
NULL;
END;
$$;
Since the ARRAY datatype can become sparse, care should be taken when performing additions or deletions of the array. Using ARRAY_COMPACT() after such operations can be helpful if the density is a concern.
The BULKCOLLECT clause, a feature of bulk SQL, returns results from SQL to PL/SQL in batches rather than one at a time.
The BULKCOLLECT clause can appear in:
SELECTINTO statement
FETCH statement
RETURNINGINTO clause of:
DELETE statement
INSERT statement
UPDATE statement
EXECUTEIMMEDIATE statement
With the BULKCOLLECT clause, each of the preceding statements retrieves an entire result set and stores it in one or more collection variables in a single operation (which is more efficient than using a loop statement to retrieve one result row at a time).
EXECUTE IMMEDIATE with Bulk Collect clause has no workarounds offered.
Note
Please note, that while the FETCH Cursor can be mostly preserved, it is advised to be changed into SELECT statements whenever possible for performance issues.
CREATEORREPLACEPROCEDURE bulk_collect_procedure ()RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},"attributes":{"component":"oracle"}}'EXECUTEASCALLERAS$$
DECLARE
--** SSC-PRF-0009 - PERFORMANCE REVIEW - CURSOR USAGE **
record_cursor CURSOR
FOR
SELECT *
FROM
bulk_collect_table;
-- !!!RESOLVE EWI!!! /*** SSC-EWI-0058 - FUNCTIONALITY FOR 'PL COLLECTION TYPE DEFINITION' IS NOT CURRENTLY SUPPORTED BY SNOWFLAKE SCRIPTING ***/!!!
-- TYPE fetch_collection_typ IS TABLE OF record_cursor%ROWTYPE;
fetch_collection_variable VARIANT !!!RESOLVE EWI!!! /*** SSC-EWI-0062 - CUSTOM TYPE 'fetch_collection_typ' USAGE CHANGED TO VARIANT ***/!!!;
-- !!!RESOLVE EWI!!! /*** SSC-EWI-0058 - FUNCTIONALITY FOR 'PL COLLECTION TYPE DEFINITION' IS NOT CURRENTLY SUPPORTED BY SNOWFLAKE SCRIPTING ***/!!!
-- TYPE collection_typ IS TABLE OF bulk_collect_table%ROWTYPE;
collection_variable VARIANT !!!RESOLVE EWI!!! /*** SSC-EWI-0062 - CUSTOM TYPE 'collection_typ' USAGE CHANGED TO VARIANT ***/!!!;
BEGIN
!!!RESOLVE EWI!!! /*** SSC-EWI-0058 - FUNCTIONALITY FOR 'RECORDS AND COLLECTIONS' IS NOT CURRENTLY SUPPORTED BY SNOWFLAKE SCRIPTING ***/!!!
SELECT * BULK COLLECT INTO collection_variable FROM bulk_collect_table;
--** SSC-PRF-0008 - PERFORMANCE REVIEW - LOOP USAGE **
FOR i IN 1 TO 0 /*collection_variable.COUNT*/!!!RESOLVE EWI!!! /*** SSC-EWI-0073 - PENDING FUNCTIONAL EQUIVALENCE REVIEW FOR 'NESTED TABLE CUSTOM TYPE EXPRESSION' NODE ***/!!!
--** SSC-PRF-0008 - PERFORMANCE REVIEW - LOOP USAGE **
LOOP
--** SSC-FDM-OR0035 - CHECK UDF IMPLEMENTATION FOR DBMS_OUTPUT.PUT_LINE_UDF. **
CALL DBMS_OUTPUT.PUT_LINE_UDF(:collection_variable(i).col1);
END LOOP;
!!!RESOLVE EWI!!! /*** SSC-EWI-OR0108 - THE FOLLOWING ASSIGNMENT STATEMENT IS NOT SUPPORTED BY SNOWFLAKE SCRIPTING ***/!!!
collection_variable := null;
OPEN record_cursor;
--** SSC-PRF-0001 - THIS STATEMENT HAS USAGES OF CURSOR FETCH BULK OPERATIONS **
record_cursor := (
CALL FETCH_BULK_COLLECTION_RECORDS_UDF(:record_cursor)
);
collection_variable := :record_cursor:RESULT;
CLOSE record_cursor;
--** SSC-PRF-0008 - PERFORMANCE REVIEW - LOOP USAGE **
FOR i IN 1 TO 0 /*collection_variable.COUNT*/!!!RESOLVE EWI!!! /*** SSC-EWI-0073 - PENDING FUNCTIONAL EQUIVALENCE REVIEW FOR 'NESTED TABLE CUSTOM TYPE EXPRESSION' NODE ***/!!!
--** SSC-PRF-0008 - PERFORMANCE REVIEW - LOOP USAGE **
LOOP
--** SSC-FDM-OR0035 - CHECK UDF IMPLEMENTATION FOR DBMS_OUTPUT.PUT_LINE_UDF. **
CALL DBMS_OUTPUT.PUT_LINE_UDF(
!!!RESOLVE EWI!!! /*** SSC-EWI-OR0036 - TYPES RESOLUTION ISSUES, ARITHMETIC OPERATION '+' MAY NOT BEHAVE CORRECTLY BETWEEN unknown AND Number ***/!!!
:collection_variable(i).col1+6);
END LOOP;
!!!RESOLVE EWI!!! /*** SSC-EWI-OR0108 - THE FOLLOWING ASSIGNMENT STATEMENT IS NOT SUPPORTED BY SNOWFLAKE SCRIPTING ***/!!!
collection_variable := null;
!!!RESOLVE EWI!!! /*** SSC-EWI-0030 - THE STATEMENT BELOW HAS USAGES OF DYNAMIC SQL. ***/!!!
EXECUTE IMMEDIATE 'SELECT * FROM
bulk_collect_table'
!!!RESOLVE EWI!!! /*** SSC-EWI-0058 - FUNCTIONALITY FOR 'EXECUTE IMMEDIATE RETURNING CLAUSE' IS NOT CURRENTLY SUPPORTED BY SNOWFLAKE SCRIPTING ***/!!!
BULK COLLECT INTO collection_variable;
--** SSC-PRF-0008 - PERFORMANCE REVIEW - LOOP USAGE **
FOR i IN 1 TO 0 /*collection_variable.COUNT*/!!!RESOLVE EWI!!! /*** SSC-EWI-0073 - PENDING FUNCTIONAL EQUIVALENCE REVIEW FOR 'NESTED TABLE CUSTOM TYPE EXPRESSION' NODE ***/!!!
--** SSC-PRF-0008 - PERFORMANCE REVIEW - LOOP USAGE **
LOOP
--** SSC-FDM-OR0035 - CHECK UDF IMPLEMENTATION FOR DBMS_OUTPUT.PUT_LINE_UDF. **
CALL DBMS_OUTPUT.PUT_LINE_UDF(
!!!RESOLVE EWI!!! /*** SSC-EWI-OR0036 - TYPES RESOLUTION ISSUES, ARITHMETIC OPERATION '+' MAY NOT BEHAVE CORRECTLY BETWEEN unknown AND Number ***/!!!
:collection_variable(i).col1+12);
END LOOP;
END;
$$;CALL bulk_collect_procedure();
1. Heavy performance issues on FETCH Cursor workaround¶
The workaround for the Fetch cursor has heavy performance requirements due to the Temporary table. It is advised for them to be manually migrated to SELECT statements
2. Execute immediate statements are not transformed¶
They are not supported by SnowConvert AI but may be manually changed to SELECT statements.
This section is a translation specification for the statement WITH subsequent to a SELECT statement which uses a BULK COLLECT INTO statement. For more information review the following documentation:
This is an approach that uses a resultset data type. User-defined types must be reviewed. Review the following Snowflake documentation to review more information about RESULTSETs.
The following example uses a User-defined type and it is declared indirectly as a table. The translation for this case implements a RESULTSET as a data type in Snowflake. The resultset is stored on a variable which must be returned wrapped on a TABLE() function.
CREATEORREPLACEPROCEDURE simple_procedure
ISTYPE salary_collection ISTABLEOFNUMBER;
v_salaries salary_collection := salary_collection();BEGINWITH IT_Employees AS(SELECT Salary
FROM MySampleTable
WHERE Department ='IT')SELECT Salary BULK COLLECT INTO v_salaries
FROM IT_Employees;END;CALL simple_procedure();
The query does not return results but the expected gathered information would be the IT Salary Information used for the example:
IT_Salary
75000
80000
Danger
One of the limitations of the RESULTSETs is that they cannot be used as tables. E.g.: select * from my_result_set; (This is an error, review the following documentation for more information).
CREATEORREPLACEPROCEDURE simple_procedure ()RETURNSSTRINGLANGUAGEJAVASCRIPTCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},{"attributes":{"component":"oracle"}}'EXECUTEASCALLERAS$$
// SnowConvert AI Helpers Code section is omitted.
!!!RESOLVE EWI!!! /*** SSC-EWI-OR0072 - PROCEDURAL MEMBER TYPE DEFINITION NOT SUPPORTED. ***/!!!
/* TYPE salary_collection IS TABLE OF NUMBER */
;
!!!RESOLVE EWI!!! /*** SSC-EWI-OR0104 - UNUSABLE VARIABLE, ITS TYPE WAS NOT TRANSFORMED ***/!!!
/* v_salaries salary_collection := salary_collection() */
;
EXEC(`SELECT Salary
FROM
MySampleTable
WHERE Department = 'IT'`);
[
!!!RESOLVE EWI!!! /*** SSC-EWI-0073 - PENDING FUNCTIONAL EQUIVALENCE REVIEW FOR 'PlBulkCollectionItem' NODE ***/!!!
//v_salaries
null,V_SALARIES] = EXEC(`SELECT
Salary
FROM IT_Employees`);
$$;CALL simple_procedure();
2. Simple case for iterations: FOR LOOP statement¶
The following case is to define a translation for iteration with FOR...LOOP. In this case, the User-defined type is implicitly a table, thus, it is possible to use a cursor to iterate. Review the following documentation to learn more:
CREATEORREPLACEPROCEDURE simple_procedure ()RETURNSVARCHARLANGUAGESQLCOMMENT='{"origin":"sf_sc","name":"snowconvert","version":{"major":1, "minor":0},"attributes":{"component":"oracle"}}'EXECUTEASCALLERAS$$
DECLARE
-- !!!RESOLVE EWI!!! /*** SSC-EWI-0058 - FUNCTIONALITY FOR 'PL COLLECTION TYPE DEFINITION' IS NOT CURRENTLY SUPPORTED BY SNOWFLAKE SCRIPTING ***/!!!
-- TYPE salary_collection IS TABLE OF NUMBER;
v_salaries VARIANT !!!RESOLVE EWI!!! /*** SSC-EWI-0062 - CUSTOM TYPE 'salary_collection' USAGE CHANGED TO VARIANT ***/!!! := salary_collection();
v_average_salary NUMBER(38, 18);
salaries_count NUMBER(38, 18);
BEGIN
salaries_count := 0;
WITH IT_Employees AS
(
SELECT Salary
FROM
MySampleTable
WHERE Department = 'IT'
)
!!!RESOLVE EWI!!! /*** SSC-EWI-0058 - FUNCTIONALITY FOR 'RECORDS AND COLLECTIONS' IS NOT CURRENTLY SUPPORTED BY SNOWFLAKE SCRIPTING ***/!!!
SELECT Salary BULK COLLECT INTO v_salaries
FROM IT_Employees;
-- Calculate the average salary
IF (null /*v_salaries.COUNT*/!!!RESOLVE EWI!!! /*** SSC-EWI-0073 - PENDING FUNCTIONAL EQUIVALENCE REVIEW FOR 'NESTED TABLE CUSTOM TYPE EXPRESSION' NODE ***/!!! > 0) THEN
v_average_salary := 0;
--** SSC-PRF-0008 - PERFORMANCE REVIEW - LOOP USAGE **
FOR i IN 1 TO 0 /*v_salaries.COUNT*/!!!RESOLVE EWI!!! /*** SSC-EWI-0073 - PENDING FUNCTIONAL EQUIVALENCE REVIEW FOR 'NESTED TABLE CUSTOM TYPE EXPRESSION' NODE ***/!!!
--** SSC-PRF-0008 - PERFORMANCE REVIEW - LOOP USAGE **
LOOP
v_average_salary :=
!!!RESOLVE EWI!!! /*** SSC-EWI-OR0036 - TYPES RESOLUTION ISSUES, ARITHMETIC OPERATION '+' MAY NOT BEHAVE CORRECTLY BETWEEN NUMBER AND salary_collection ***/!!!
:v_average_salary + v_salaries(i);
salaries_count := :salaries_count + 1;
END LOOP;
v_average_salary := :v_average_salary / :salaries_count;
END IF;
-- Display the average salary
--** SSC-FDM-OR0035 - CHECK UDF IMPLEMENTATION FOR DBMS_OUTPUT.PUT_LINE_UDF. **
CALL DBMS_OUTPUT.PUT_LINE_UDF('Average Salary for IT Department: ' || NVL(:v_average_salary :: STRING, ''));
END;
$$;CALL simple_procedure();
There are limitations while using the RESULTSET data type. Review the following Snowflake documentation to learn more. Markable limitations are the following:
Declaring a column of type RESULTSET.
Declaring a parameter of type RESULTSET.
Declaring a stored procedure’s return type as a RESULTSET.
2. Execute statements with Bulk Collect clause are not supported.¶