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:"
Related
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);
$$
;
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.
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;
$$
;
I need to rewrite a SQL scalar function which is called in power bi reports
how can I achieve the following in snowflake?
create function test (#input varchar(10))
returns varchar
(
declare #text varchar(50)
declare #cnt int
select #cnt = max(key) from tbl;
if #input = 'table1'
begin
select #text = text from table1 --statements will be different based on input passed and is from tables
end
if #input = 'table12'
begin
select #text = text from table12 where id = #cnt
end
...
return #text
)
Can you please share some examples where we can declare /set variables and also execute sql statements based on input parameter passed ?
Thanks,
Jo
I think stored procedure works better in your case. I tried below examples and hope it is what are you after:
create or replace table test (a int);
insert into test values (1), (2);
create or replace table table1 (a varchar, id int);
insert into table1 values ('table1', 1);
create or replace table table2 (a varchar, id int);
insert into table2 values ('table2', 2);
CREATE OR REPLACE PROCEDURE my_proc(INPUT VARCHAR)
RETURNS VARCHAR
LANGUAGE JAVASCRIPT
AS
$$
var stmt = snowflake.createStatement( {sqlText: "SELECT MAX(a) FROM test;"} );
var resultSet = stmt.execute();
resultSet.next();
var id = resultSet.getColumnValue(1);
stmt = snowflake.createStatement( {sqlText: "SELECT a FROM " + INPUT + " WHERE id = " + id + ";"} );
resultSet = stmt.execute();
resultSet.next();
return resultSet.getColumnValue(1);
$$ ;
call my_proc('table2');
-- it returns "table2"
I have omitted any validations for simplicity.
Cheers
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)
$$;