I've encountered a problem with parameters binding and bulk inserting in SP. I have no idea why it's not working. According to docs it should work.
CREATE TABLE TEST(
COL1 NUMBER(38,0),
COL2 NUMBER(38,0)
);
CREATE OR REPLACE PROCEDURE SP_TEST()
RETURNS FLOAT NOT NULL
LANGUAGE JAVASCRIPT
AS $$
var stmt = snowflake.createStatement({
sqlText: "INSERT INTO TEST(COL1, COL2) VALUES(?, ?)",
binds: [[1, 2], [3, 4]]
});
stmt.execute();
return 0;
$$
;
CALL SP_TEST();
Execution error in store procedure SP_TEST: Unsupported type for binding
argument 1,2 At Snowflake.createStatement, line 3 position 22
I could not find any sample for entering multiple rows in the documents. In case, you want to use multiple values, then you can use the following syntax:
CREATE OR REPLACE PROCEDURE SP_TEST()
RETURNS FLOAT NOT NULL
LANGUAGE JAVASCRIPT
AS $$
var stmt = snowflake.createStatement({
sqlText: "INSERT INTO TEST(COL1, COL2) VALUES(?, ?),(?, ?)",
binds: [1, 2, 3, 4]
});
stmt.execute();
return 0;
$$
;
As you can see, I removed the extra brackets in binds, and added a new "(?,?)" to the values part for the second row.
Try this workaround:
CREATE PROCEDURE SP_TEST()
RETURNS FLOAT NOT NULL
LANGUAGE JAVASCRIPT
AS $$
const vals = [[1, 2], [3,4]]
const binds = vals.reduce((a, b) => a.concat(b))
const placeholders = vals.map(()=>"(?, ?)").join(",")
const stmt = snowflake.createStatement({
sqlText: "INSERT INTO TEST(COL1, COL2) VALUES " + placeholders,
binds
})
stmt.execute()
return 0
$$
The idea is to flatten the array, and then dynamically append the correct number of placeholders.
Related
I am trying to create a stored procedure in snowflake that create users in snowflake. But I am unable to bind variables.
Here is what I tried using snowflake scripting. I was not successful in creating a version that works
create or replace procedure create_user(user_name varchar, password_value varchar)
returns varchar
language sql
as
$$
begin
create user :USER_NAME password = :password_value;
return 'Completed.';
end;
$$
;
In the above stored procedure, when I use :USER_NAME (I have tried upper and lower case). I get an error saying unexpected :. Password_value seems to work correctly.
I have also tried javascript version
CREATE OR REPLACE PROCEDURE security.create_user_v2(USER_NAME varchar, PASSWORD_VALUE varchar)
RETURNS BOOLEAN
LANGUAGE JAVASCRIPT
AS
$$
var cmd = "create user :1 password = :2;";
var stmt = snowflake.createStatement(
{
sqlText: cmd,
binds: [USER_NAME, PASSWORD_VALUE]
}
);
var result = stmt.execute();
result.next();
return true
$$
;
I get the same here as well, says unexpected :.
This version works, but this is concatenation, i want to avoid this (possible sql injection)
CREATE OR REPLACE PROCEDURE security.create_user_v2(USER_NAME varchar, PASSWORD_VALUE varchar)
RETURNS BOOLEAN
LANGUAGE JAVASCRIPT
AS
$$
var cmd = "create user "+USER_NAME+" password = :1;";
var stmt = snowflake.createStatement(
{
sqlText: cmd,
binds: [PASSWORD_VALUE]
}
);
var result = stmt.execute();
result.next();
return true
$$
;
How do I create a snowflake scripting version or javascript version that bind variables when creating users in stored procedure.
User name must be enclosed with IDENTIFIER in order to use variable:
CREATE OR REPLACE PROCEDURE security.create_user_v2(USER_NAME varchar,
PASSWORD_VALUE varchar)
RETURNS BOOLEAN
LANGUAGE JAVASCRIPT
AS
$$
var cmd = "create user IDENTIFIER(:1) password = :2;";
var stmt = snowflake.createStatement(
{
sqlText: cmd,
binds: [USER_NAME, PASSWORD_VALUE]
}
);
var result = stmt.execute();
result.next();
return true
$$
;
Call:
CALL security.create_user_v2('user1', 'password123');
CALL security.create_user_v2('"user1#x.com"', 'password123');
SHOW USERS;
Snowflake Scripting:
create or replace procedure security.create_user(user_name varchar,
password_value varchar)
returns varchar
language sql
as
$$
begin
create user IDENTIFIER(:USER_NAME) password = :password_value;
return 'Completed.';
end;
$$;
may i know how to pass variant data into snowflake table using snowflake stored procedure .
CREATE
OR REPLACE PROCEDURE abc(
MY_ID STRING,
P_FILTERS VARIANT
) RETURNS VARIANT
LANGUAGE JAVASCRIPT as $$
try
{
var P_FILTERS=P_FILTERS;
var query=" INSERT INTO abc (SQ_ID,id,\
FILTERS,\
ITERATION)\
VALUES (abc.nextval,\
:1,\
:2,\
0); "
var sql = snowflake.createStatement( {sqlText: query,binds:[MY_ID,P_FILTERS] });
var resultSet = sql.execute();
COMMIT;
}
catch(error)
{
return (error);
}
$$;
can some one help and undestand in letting me know
Thanks,
Nikhil
The solution isn't totally straightforward, but going from variant to string to variant solves the problem:
create or replace temp table variants as
select 'a'::string id, parse_json('{"hello":"world"}') v
;
CREATE
OR REPLACE PROCEDURE insert_variant(
MY_ID STRING,
P_FILTERS VARIANT
) RETURNS VARIANT
LANGUAGE JAVASCRIPT as $$
var query="INSERT INTO variants (id, v) select :1, parse_json(:2); "
var sql = snowflake.createStatement({
sqlText: query
, binds:[MY_ID, JSON.stringify(P_FILTERS)]
});
var resultSet = sql.execute();
$$;
call insert_variant('c', parse_json('{"hello2":"world3"}'))
;
This because
Currently, only JavaScript variables of type number, string, and SfDate can be bound. https://docs.snowflake.com/en/sql-reference/stored-procedures-usage.html#binding-variables
And trying to parse JSON on the VALUES() part of an insert gives you the error "Invalid expression in VALUES clause". But having an INSERT+SELECT solves it.
If I run an INSERT/SELECT in Snowflake through the javascript API, it looks like the rowCount returned is 1 regardless of how many rows were inserted...
myStatement = snowflake.createStatement( {sqlText: mySql} );
myStatement.execute();
rowCount = myStatement.getRowCount();
Is there a SIMPLE way to retrieve the number of rows inserted?
Am I doing something wrong?
Thanks
No you are not doing something wrong. There is a difference in Snowflake from executing a query like an SELECT * .... from executing an UPDATE\DELETE\INSERT statement.
In snowflake if you perform a select, and what to know the number of returned rows then you are correct you execute:
myStatement = snowflake.createStatement( {sqlText: "SELECT * FROM TABLE1"} );
myStatement.execute();
rowCount = myStatement.getRowCount();
When you execute a 'INSERT\UPDATE\DELETE` snowflake returns just one row that as indicated on answer by #greg-pavlik has the number of modified rows.
However there is even a more simple way.
myStatement = snowflake.createStatement( {sqlText: "INSERT ..."} );
myStatement.execute();
rowCount = myStatement.getNumRowsAffected();
When you insert rows outside of a stored procedure, you will get a single row in return that tells you the number of rows inserted. The same will happen in a stored procedure, so you need to grab the result set and read the number in the first row, first column of that result set.
create or replace temp table foo(v string);
-- This returns "number of rows", 2
insert into foo(v) values ('1'), ('2');
create or replace procedure foo()
returns string
language javascript
as
$$
var mySql = "insert into foo(v) values ('1'), ('2');";
var myStatement = snowflake.createStatement( {sqlText: mySql} );
var rs = myStatement.execute();
rs.next()
return "Inserted " + rs.getColumnValue(1) + " rows.";
$$;
call foo();
If i have created/generated a list of elements during processing in stored procedure say rownum = [1,2,3,4].
Now i want to use this list in a sql statement to filter out rows say select * from mytable where rownum not in (1,2,3,4) in same stored procedure.
How can i achieve this ?
Please guide.
Thanks
The general solution to this would be to use binding variables. However, set types are not supported as bind variables in the Stored Procedure APIs currently.
The JavaScript APIs do permit you to generate your SQL dynamically using string and array transform functions, so the following approaches can be taken to work around the problem.
Inline the list of values into the query by forming a SQL syntax of a set of values:
CREATE OR REPLACE PROCEDURE SAMPLE()
RETURNS RETURNTYPE
LANGUAGE JAVASCRIPT
AS
$$
var lst = [2, 3, 4]
var lstr = lst.join(',') // "2, 3, 4"
var sql_command = `SELECT * FROM T WHERE C NOT IN (${lstr})` // (2, 3, 4)
var stmt = snowflake.createStatement( {sqlText: sql_command} )
// Runs: SELECT * FROM T WHERE C NOT IN (2, 3, 4) [Literal query string]
[...]
$$;
Or if the list of values used could be unsafe, you can generate the query to carry just the right number of bind variables:
CREATE OR REPLACE PROCEDURE SAMPLE()
RETURNS RETURNTYPE
LANGUAGE JAVASCRIPT
AS
$$
var lst = [2, 3, 4]
var lst_vars = Array(lst.length).fill("?").join(", ") // "?, ?, ?"
var sql_command = `SELECT * FROM T WHERE C NOT IN (${lst_vars})` // (?, ?, ?)
var stmt = snowflake.createStatement( {sqlText: sql_command, binds: lst} )
// Runs: SELECT * FROM T WHERE C NOT IN (2, 3, 4) [After bind-substitution]
[...]
$$;
Snowflake has the ARRAY_CONTAINS( , ) function.
example:
Array_Contains( 5, array_construct( 1,2,3,4))
In a snowflake stored procedure I am executing CTAS statements and want to retrieve the number of rows in the resultant object. We don't have access to QUERY_HISTORY (we get an error), and RESULT_SCAN(LAST_QUERY_ID()) doesn't help either (it gives us back the Table xyz Created result, but does not have meta-data i.e. number of rows created).
I can do it with a Select Count(*) in a separate query, but that seems to be a hack since the Row Count is right there in the History.
CREATE OR REPLACE PROCEDURE EDW_ADMIN.DAG_TEST()
RETURNS VARCHAR(512)
LANGUAGE JAVASCRIPT
AS
$$
{
let strCTAS = "";
let rsCTAS;
let rsRowsAffected;
let rowsAffected = 0;
strCTAS = "CREATE OR REPLACE TABLE EDW_ADMIN.DEMO_PROC_TEMP AS SELECT * FROM RAW_BIR.H_RPTUNIT;";
rsCTAS = snowflake.execute( {sqlText: strCTAS} );
// This works in a Query Worksheet in the browser, but gives me the following error when called from a procedure
// "[Stored procedure execution error: Requested information on the current user is not accessible in stored procedure.]"
rsRowsAffected = snowflake.execute( {sqlText: "SELECT ROWS_PRODUCED FROM TABLE(INFORMATION_SCHEMA.QUERY_HISTORY(RESULT_LIMIT=>100)) WHERE QUERY_ID = LAST_QUERY_ID();"} );
rsRowsAffected.next();
rowsAffected = rsRowsAffected.getColumnValue(1);
// This works, but you are doing execute i/o which is really un-necessary
// rsRowsAffected = snowflake.execute( {sqlText: "SELECT COUNT(*) FROM EDW_ADMIN.DEMO_PROC_TEMP;"} );
// rsRowsAffected.next();
// rowsAffected = rsRowsAffected.getColumnValue(1);
// This does NOT work, RESULT_SCAN has no metadata associated with it, this returns "Table DEMO_PROC_TEMP successfully created."
// rsRowsAffected = snowflake.execute ( {sqlText: "SELECT * FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));" } );
// rsRowsAffected.next();
// rowsAffected = rsRowsAffected.getColumnValue(1);
return rowsAffected;
}
$$
;
CALL EDW_ADMIN.DAG_TEST();
DROP EDW_ADMIN.DEMO_PROC_TEMP;
DROP PROCEDURE EDW_ADMIN.DAG_TEST();
Try adding execute as caller to the stored procedure declaration. For example:
create or replace procedure p()
returns text
language javascript
execute as caller
as
$$
const stmt1 = snowflake.createStatement( { sqlText: "create or replace table t as select $1 x from values (1),(2),(3)" } )
const rs1 = stmt1.execute()
const stmt2 = snowflake.createStatement( { sqlText: "SELECT ROWS_PRODUCED FROM TABLE(INFORMATION_SCHEMA.QUERY_HISTORY(RESULT_LIMIT=>100)) WHERE QUERY_ID = LAST_QUERY_ID()" } )
const rs2 = stmt2.execute()
rs2.next()
const rowsAffected = rs2.getColumnValue(1)
return rowsAffected
$$
;
call p();
returns 3