Transaction in snowflake stored procedures - snowflake-cloud-data-platform

My first stored proc in snowflake, trying to use transaction in one of the sample stored proc, please help to fix the error and why? "Uncaught SyntaxError: Unexpected identifier in GET_ROW_COUNT at ' BEGIN TRANSACTION;' position 8"
trying to follow the document: https://docs.snowflake.com/en/sql-reference/transactions.html
Here is the proc.
create or replace procedure get_row_count(table_name VARCHAR)
returns float not null
language javascript
as
$$
var row_count = 0;
BEGIN TRANSACTION;
// Dynamically compose the SQL statement to execute.
var sql_command = "select count(*) from " + TABLE_NAME;
// Run the statement.
var stmt = snowflake.createStatement(
{
sqlText: sql_command
}
);
var res = stmt.execute();
// Get back the row count. Specifically, ...
// ... get the first (and in this case only) row from the result set ...
res.next();
// ... and then get the returned value, which in this case is the number of
// rows in the table.
row_count = res.getColumnValue(1);
return row_count;
COMMIT ;
$$
;

You need to call COMMIT and BEGIN TRANSACTION using snowflake.execute() as they are SQL commands:
create or replace procedure get_row_count(table_name VARCHAR)
returns float not null
language javascript
as
$$
var row_count = 0;
snowflake.execute({ sqlText: 'BEGIN TRANSACTION' });
// Dynamically compose the SQL statement to execute.
var sql_command = "select count(*) from " + TABLE_NAME;
// Run the statement.
var stmt = snowflake.createStatement(
{
sqlText: sql_command
}
);
var res = stmt.execute();
// Get back the row count. Specifically, ...
// ... get the first (and in this case only) row from the result set ...
res.next();
// ... and then get the returned value, which in this case is the number of
// rows in the table.
row_count = res.getColumnValue(1);
snowflake.execute({ sqlText: 'COMMIT' });
return row_count;
$$
;

Related

Simple Stored procedure in Snowflake Scripting

I need to create a table in snowflake stored procedure using sql. Below is the code
create or replace procedure sp(tmp_table varchar,col_name varchar,d_type varchar )
returns varchar not null
as
$$
BEGIN
drop table if exists identifier(:tmp_table);
create table identifier(:tmp_table) (identifier(:col_name) identifier(:d_type));
END;
$$;
I am getting the error as
syntax error line 4 at position 24 unexpected '('. (line 4)
Could you please help me on this issue?
Bind variables are not supported in columns, this is why your script fails. You can use EXECUTE IMMEDIATE to generate a dynamic SQL to overcome this issue:
create or replace procedure sp(tmp_table varchar,col_name varchar,d_type varchar )
returns varchar not null
as
$$
BEGIN
drop table if exists identifier(:tmp_table);
execute immediate 'create table ' || :tmp_table || '(' || :col_name || ' ' || :d_type || ')' ;
END;
$$;
Unfortunately, it isn't possible to dynamically name columns in this way using Snowflake Scripting [1]. As an alternative you can dynamically generate your SQL statements as text to then execute.
I've swapped the drop table for create or replace as it does the same function, but in one command.
create or replace procedure sp(tmp_table varchar, col_name varchar, d_type varchar)
returns table (result varchar)
language sql
as
DECLARE
sql_text varchar;
rs resultset;
invalid_input EXCEPTION (-20001, 'Input contains whitespace.');
BEGIN
IF ((SELECT :tmp_table regexp '(^\\S*$)')=FALSE) THEN
RAISE invalid_input;
END IF;
IF ((SELECT :col_name regexp '(^\\S*$)')=FALSE) THEN
RAISE invalid_input;
END IF;
IF ((SELECT :d_type regexp '(^\\S*$)')=FALSE) THEN
RAISE invalid_input;
END IF;
sql_text := 'create or replace table ' || :tmp_table || '(' || :col_name || ' ' || :d_type || ')' ;
rs := (execute immediate :sql_text);
return table(rs);
END;
Note: In the example above I've included some code to check for whitespace in the inputs to minimise the potential for SQL injection. This is important to stop users abusing the procedure. Additional checks would be prudent. You should also ensure that this Stored Procedure runs as the least privilege role possible to further minimise the scope for abuse.
Example as JavaScript (without SQL injection protection):
CREATE OR REPLACE procedure sp(TMP_TABLE varchar, COL_NAME varchar, D_TYPE varchar)
RETURNS varchar not null
LANGUAGE javascript
AS
$$
var sql_cmd = "DROP TABLE IF EXISTS " + TMP_TABLE + ";";
var stmt = snowflake.createStatement(
{sqlText: sql_cmd}
);
var res = stmt.execute();
sql_cmd = "CREATE TABLE " + TMP_TABLE + " (" + COL_NAME + " " + D_TYPE + ");";
stmt = snowflake.createStatement(
{sqlText: sql_cmd}
);
res = stmt.execute();
res.next();
return res.getColumnValue(1);
$$
;

how to run simple select query and get reusult in snowflakes procedure?

how to run simple select query and get reusult in snowflakes procedure?
CREATE OR REPLACE PROCEDURE COARSDB0P_DB.COUNT_MISMATCH()
RETURNS VARCHAR
LANGUAGE JAVASCRIPT
AS
$$
var command ="SELECT b.TABLE_CATALOG,b.TABLE_SCHEMA,b.TABLE_NAME,b.TABLE_TYPE,b.ROW_COUNT as Dev_COUNT,a.ROW_COUNT as UAT_COUNT FROM TABLE A ,information_schema.tables B where b.TABLE_NAME=a.TABLE_NAMES and b.TABLE_SCHEMA=a.TABLE_SCHEMA and b.TABLE_TYPE=a.TABLE_TYPE and TRY_CAST(b.ROW_COUNT as NUMBER) != TRY_CAST(a.ROW_COUNT as NUMBER);"
var cmd1_dict = {sqlText: command};
var stmt = snowflake.createStatement(cmd1_dict);
var rs = stmt.execute();
rs.next();
var output = rs.getColumnValue();
return output;
$$;
I need to return the actualy output of mentioned SLECT query.
See Returning tabular data from a stored procedure. Note this is for a SQL stored procedure, not Javascript. You will want something like:
create procedure ...
returns table (catalog varchar, schema varchar, ...)
declare
query varchar;
res resultset;
begin
-- build the dynamic SQL query
query := 'select ...'
res := (execute immediate :query);
return table (res);
end;
You can use Tabular SQL UDFs to return the result:
https://docs.snowflake.com/en/developer-guide/udf/sql/udf-sql-tabular-functions.html#calling-a-sql-udtf

Snowflake Stored procedure with dynamic SQL statement Error

I am testing following test sp , Proc is complaining about sql command
JavaScript compilation error: Uncaught SyntaxError: Unexpected string in TEST_PROC at ' var sqlCommand
What i would like to be done in this proc is, Run select on table and prepare alter statements and execute all statements.
CREATE OR REPLACE PROCEDURE test_proc()
RETURNS STRING
LANGUAGE javascript
AS
$$
var sqlCommand = "select ''ALTER EXTERNAL TABLE''|| '' '' || SCHEMA_NAME ||''.'' || TABLE_NAME ||'' ''|| ''REFRESH'' ||'' ''''''|| LOCATION ||''''''''
from EXT_TABLE_CONGIG
where TABLE_NAME =''TABLEXYZ'';"
var stmt = snowflake.createStatement({ sqlText: sqlCommand } );
stmt.execute();
return 'success'
$$;```
You cannot define a multi-line string using double quotes in JavaScript. There's also a quote balance issue.
Using backquotes (backticks) allows multi-line strings and use of either single or double quotes without having to double them.
CREATE OR REPLACE PROCEDURE test_proc()
RETURNS STRING
LANGUAGE javascript
AS
$$
var sqlCommand = `select 'ALTER EXTERNAL TABLE' || ' ' || SCHEMA_NAME || '.' || TABLE_NAME || ' ' || 'REFRESH' || '''' || LOCATION || ''''
from EXT_TABLE_CONGIG
where TABLE_NAME = 'TABLEXYZ';`
var stmt = snowflake.createStatement({ sqlText: sqlCommand } );
var rs = stmt.execute();
rs.next();
var sql = rs.getColumnValue(1);
stmt = snowflake.createStatement({ sqlText: sql });
stmt.execute();
return 'success';
$$;
call test_proc();
Below example is to demonstrate run dynamic SQL in procedure -
Answer from #Greg addresses multi-line in java script already.
CREATE OR REPLACE PROCEDURE test_proc()
RETURNS STRING
LANGUAGE javascript
AS
$$
var sqlCommand = "select 'alter table '||tname||' add (id number)' from t_name where tname in ('t1','t2','t3')";
var stmt = snowflake.createStatement({ sqlText: sqlCommand } );
var v_result = stmt.execute();
while(v_result.next()) {
var stmt1 = snowflake.createStatement({ sqlText: v_result.getColumnValue(1) } );
stmt1.execute();
var f_result = v_result.getColumnValue(1);
};
return 'success';
$$
;
Also refer -
Read through - "Here’s an example that retrieves a ResultSet and iterates through it:"

Snowflake SQL Procedure to Alter all tables in a database or schema

I'm trying to write a snowflake sql stored procedure to alter all tables in a database.
I know we can get a table names from querying information schema
eg: select table_name from DEMO_DB.INFORMATION_SCHEMA.TABLES;
I can do it for one table like below
create or replace procedure enable_change_tracking(TABLE_NAME varchar)
returns varchar
language javascript
as
$$
var my_sql_command = "ALTER table "+ TABLE_NAME +" SET CHANGE_TRACKING = TRUE;"
var statement1 = snowflake.createStatement( {sqlText: my_sql_command} );
var result_set1 = statement1.execute();
return 'Done.';
$$
;'''
call enable_change_tracking('table_name');
How do i pass the result set of '''select table_name from DEMO_DB.INFORMATION_SCHEMA.TABLES;''' to a above stored procedure?
As Greg mentioned, you need to loop through the result in the SP:
create or replace procedure enable_change_tracking()
returns varchar
language javascript
as
$$
var my_sql_command = "select table_name from DEMO_DB.INFORMATION_SCHEMA.TABLES"
var statement1 = snowflake.createStatement( {sqlText: my_sql_command} );
var r = statement1.execute();
while(r.next()) {
table_name = r.getColumnValue('TABLE_NAME');
sub_q = "ALTER table "+ table_name +" SET CHANGE_TRACKING = TRUE;"
// run your query here
}
return a;
$$
;
Answer
create or replace procedure enable_change_tracking_st()
returns varchar
language javascript
as
$$
var my_sql_command = "select table_name from DEMO_DB.INFORMATION_SCHEMA.TABLES where table_schema = 'PUBLIC'"
var statement1 = snowflake.createStatement( {sqlText: my_sql_command} );
var r = statement1.execute();
while(r.next()) {
var table_name = r.getColumnValue(1);
var sub_q = "ALTER table "+ table_name +" SET CHANGE_TRACKING = TRUE;"
var statement2 = snowflake.createStatement( {sqlText: sub_q} );
var r2 = statement2.execute();
}
return 'Done';
$$
;
I generally write something to produce a script. Usually a simple UDF which takes the table name and the desired DDL statement. That lets me test it without running it against everything. Once I've tested it, I then do something along the lines of:
SELECT UDF_GEN_MY_QUERT(TABLE_NAME) FROM (SELECT TABLE_NAME FROM ...).
As an example, I have a meta-data table to created Snowflake object from Salesforce Object meta-data.
On Friday, I deployed 168 new/updated tables, VIEWs, STREAM and TASKs using this technique, into 4 databases (3 dev DBs, 1 INT DB.)
I will be publishing an article on Linkedin, Snowflake medium blog and my own wordpress blog shortly.

Uppercase SQL statements don't work in Snowflake stored procedures

In the example below the working_one stored procedure works, while the broken_one does not. The only difference between the two is letters case of SQL statement.
create table tmp (
raw_json variant
);
-- 2019-01-01 = 1546347600000
-- 2018-01-01 = 1514811600000
insert into tmp select parse_json('{ "timestamp":1514811600000}');
create or replace procedure working_one(TIME_VALUE varchar)
returns varchar
language javascript
as
$$
var stmtString = "delete from tmp where to_timestamp(raw_json:timestamp::string) < to_timestamp(:1);"
var stmt = snowflake.createStatement({sqlText: stmtString, binds: [TIME_VALUE]})
var rs = stmt.execute()
rs.next()
return rs.getColumnValue(1)
$$;
create or replace procedure broken_one(TIME_VALUE varchar)
returns varchar
language javascript
as
$$
var stmtString = "DELETE FROM TMP WHERE TO_TIMESTAMP(RAW_JSON:TIMESTAMP::STRING) < TO_TIMESTAMP(:1);"
var stmt = snowflake.createStatement({sqlText: stmtString, binds: [TIME_VALUE]})
var rs = stmt.execute()
rs.next()
return rs.getColumnValue(1)
$$;
call broken_one('1546347600000');
call working_one('1546347600000');
I don't believe the problem is in the case-sensitivity of the SQL, or even that it's a Stored Procedure. The issue is that the attribute inside your JSON is case-sensitive. Try this and tell me if that works better for you.
create or replace procedure fixed_one(TIME_VALUE varchar)
returns varchar
language javascript
as
$$
var stmtString = "DELETE FROM TMP WHERE TO_TIMESTAMP(RAW_JSON:timestamp::STRING) < TO_TIMESTAMP(:1);"
var stmt = snowflake.createStatement({sqlText: stmtString, binds: [TIME_VALUE]})
var rs = stmt.execute()
rs.next()
return rs.getColumnValue(1)
$$;

Resources