Logging a procedure information into a table in snowflake - snowflake-cloud-data-platform

I have a table that logs certain information about procedures that are called
PROCEDURE_NAME, PARAMETERS_PASSED, START_TIME,END_TIME, ERROR_MESSAGE ... and others
I am creating a procedure which inserts into this table the information I want.
However, I do not know how I am to pass the error message from the stored procedure that is called
into my new procedure
Is there a way to save this error message
EX)
SP1 - STARTS
SP1 - ENDS & THERE IS AN ERROR
LOG_STORED_PROCEDURE(SP1_NAME,SP1_PARAMETERS,SP1_START_TIME,SP1_END_TIME,ERROR_MESSAGE)

Use JavaScript's try/catch block and do the insert into an error table in the catch block. Here's a sample you can modify:
create or replace procedure catch_error_example()
returns string
language javascript
execute as caller
as
$$
// Try to insert into a non-existent table.
try{
cmd = {sqlText: "insert into NON_EXISTENT_TABLE select 1"};
stmt = snowflake.createStatement(cmd);
stmt.execute();
} catch (e) {
cmd = {sqlText: `insert into ERROR_LOG (STORED_PROC, ERROR_MESSAGE) values ('CATCH_ERROR_EXAMPLE', '${escapeString(e.message)}')`};
stmt = snowflake.createStatement(cmd);
stmt.execute();
}
return "Check error log.";
function escapeString(value) {
var s = value.replace(/\\/g, "\\\\");
s = s.replace(/'/g, "''" );
s = s.replace(/"/g, '\\"');
s = s.replace(/\s+/g, " ");
return s;
}
$$;
create temp table ERROR_LOG(ERROR_TIME timestamp default current_timestamp, STORED_PROC string, ERROR_MESSAGE string);
call catch_error_example();
select * from ERROR_LOG;

Related

Snowflake SQL compilation for a stored procedure creating a TASK with block statement

I'm trying to come up with a SP that creates a certain task, appended by year, for a generic approach.
I can create the tasks outside, alone, with the $$ marks,
but I can't do it inside JS SP like this:
CREATE OR REPLACE PROCEDURE create_exec_tasks_by_year_range()
RETURNS varchar
LANGUAGE JAVASCRIPT
EXECUTE AS OWNER
AS
$$
var return_value = "";
var range_years = Array.from(Array(new Date().getUTCFullYear() - 2006), (_, i) => (i + 2007).toString());
//CREATE a task TO CALL SP BY YEAR, FROM 2007-current year
range_years.forEach((year_elem) => {
rs = snowflake.createStatement( {
sqlText: `CREATE OR REPLACE TASK MY_TSK_YEAR_`+year_elem+`
SCHEDULE = 'USING CRON 30 22 * * SUN UTC'
AS
EXECUTE IMMEDIATE
$$
DECLARE
year_track float;
rs resultset;
BEGIN
year_track := :1;
rs := (execute IMMEDIATE 'INSERT INTO MY_TABLE VALUES(?)' using (year_track));
return TABLE(rs);
END;
$$
;`
, binds: [year_elem] }).execute();
rs.next();
//rs.getColumnValue(1);
return_value += "MY_TSK_YEAR_"+year_elem+", ";
to_exec = snowflake.createStatement( {
sqlText: `EXECUTE TASK MY_TSK_YEAR_`+year_elem+`
to_exec.next();
return_value += to_exec.getColumnValue(1)+", ";
});
return return_value;
$$;
because it throws me
syntax error line ...at position 2 unexpected 'DECLARE'
while creating the TASK manually, works, because I don't have a conflict between $$?
CREATE OR REPLACE TASK SHARED.SRC_EXT_WEATHER.TSK_DUMMY
SCHEDULE = 'USING CRON 30 22 * * SUN UTC'
AS
EXECUTE IMMEDIATE
$$
DECLARE
year_track float;
rs resultset;
BEGIN
year_track := 2007;
rs := (execute IMMEDIATE 'INSERT INTO MY_TABLE VALUES(?)' using (year_track));
return TABLE(rs);
END;
$$;
Is it possible to make the SP to work for the TASK created with EXECUTE_IMMEDIATE block and bind parameter? The problem seems to be the way I write in inside the $$ scope of the stored procedure, no?
The problem is you're using the $$ string terminator for the stored procedure. Then inside the body of the stored procedure you're using $$ to terminate the string containing the body of the task. As soon as the compiler runs into that, it sees it as the end of the string defining the stored procedure body. To use $$ in the body of a stored procedure, you can do something like this:
CREATE OR REPLACE PROCEDURE create_exec_tasks_by_year_range()
RETURNS varchar
LANGUAGE JAVASCRIPT
EXECUTE AS OWNER
AS
$$
const double_dollar = '$' + '$';
var return_value = "";
var to_exec;
var rs_exec;
var range_years = Array.from(Array(new Date().getUTCFullYear() - 2006), (_, i) => (i + 2007).toString());
//CREATE a task TO CALL SP BY YEAR, FROM 2007-current year
range_years.forEach((year_elem) => {
rs = snowflake.createStatement( {
sqlText: `CREATE OR REPLACE TASK MY_TSK_YEAR_${year_elem}
WAREHOUSE = MOS_WEATHER
SCHEDULE = 'USING CRON 30 22 * * SUN UTC'
AS
EXECUTE IMMEDIATE
${double_dollar}
DECLARE
year_track float;
rs resultset;
BEGIN
year_track := :1;
rs := (execute IMMEDIATE 'INSERT INTO MY_TABLE VALUES(?)' using (year_track));
return TABLE(rs);
END;
${double_dollar};`
, binds: [year_elem] }).execute();
rs.next();
//rs.getColumnValue(1);
return_value += "MY_TSK_YEAR_"+year_elem+", ";
to_exec = snowflake.createStatement({sqlText: `EXECUTE TASK MY_TSK_YEAR_${year_elem}`});
rs_exec = to_exec.execute();
rs_exec.next();
return_value += rs_exec.getColumnValue(1)+", ";
});
return return_value;
$$;
#Greg Pavlik I don't know my final approach is OK, but it's working, as far as I run this on SF UI!
The above code, for me worked with a minor change from :1 to ${year_elem} , but then I face issue with binding a string value (single quoted) ... like this:
CREATE OR REPLACE PROCEDURE create_exec_tasks_by_year_range(str_arg string)
RETURNS varchar
LANGUAGE JAVASCRIPT
COMMENT ='[DRAFT] SP that creates tasks since a certain rangw with exec immediate block using 2 args: one is year in range, other is a string to isnert as well'
EXECUTE AS OWNER
AS
$$
const double_dollar = '$' + '$';
var return_value = "";
var to_exec;
var rs_exec;
var arg = STR_ARG;
var range_years = Array.from(Array(new Date().getUTCFullYear() - 2020), (_, i) => (i + 2021).toString());
//CREATE a task TO CALL SP BY YEAR, FROM 2007-current year
range_years.forEach((year_elem) => {
rs = snowflake.createStatement( {
sqlText: `CREATE OR REPLACE TASK MY_TSK_YEAR_${year_elem}
SCHEDULE = 'USING CRON 30 22 * * SUN UTC'
AS
EXECUTE IMMEDIATE
${double_dollar}
DECLARE
year_track float;
rs resultset;
my_str varchar;
BEGIN
year_track := ${year_elem};
my_str:= ${arg} ;
rs := (execute IMMEDIATE 'INSERT INTO Z_TEST_TASK_INSERT2 VALUES(?,?)' using (year_track,my_str));
return TABLE(rs);
END;
${double_dollar};`
//, binds: [year_elem]
}).execute();
rs.next();
return_value += rs.getColumnValue(1);
});
return return_value;
$$;
CALL create_exec_tasks_by_year_range('testing_String');
EXECUTE TASK MY_TSK_YEAR_2021;
EXECUTE TASK MY_TSK_YEAR_2022;
000904 SQL compilation error: error line 8 at position 21
invalid identifier 'testing_String'
One thing I would like to understand is:
How to use bind variable as a single quoted string? How to escape it or at leats distinguish it from the query prefixed by ' alread?
The SP creation succeeds via SQL Editor (DBEaver for instance) or SF UI
But the CALL is only working for me on SF UI (and SNOWSQL must happen the same, I assume). Is that something to do with the drivers used by JDBC vs. SF UI?

Snowflake Stored Procedure For Loop

i'm working with Snowflake,
i created this Stored Procedure, it's purpose is to do the following steps:
extract the relevant 'application_id' values from the SQL query
use a FOR loop over a SQL query, with the 'application_id' values that i extracted
in step 1, and delete the relevant rows from the target table.
when i call the Stored Procedure, it runs without errors, but it doesn't do anything ( the relevant records are not been deleted).
i added my SP code,
please let me know if you see any syntax / logic errors,
thanks
CREATE OR REPLACE PROCEDURE DWH.sp_testing()
RETURNS string
LANGUAGE javascript
strict
EXECUTE AS owner
AS
$$
try
{
var application_list = ` SELECT application_id
FROM PUBLIC.my_source_table_name
WHERE my_flag = 1
`
var query_statement = snowflake.createStatement({sqlText: application_list});
var application_list_result = query_statement.execute();
for (i = 1; i <= query_statement.getRowCount(); i++)
{
application_list_result.next()
application_id = application_list_result.getColumnValue(1)
var delete_daily_records = `
DELETE FROM PUBLIC.my_target_table_name AS target
WHERE target.application_id = ${application_id}
`
snowflake.execute({sqlText: delete_daily_records});
}
}
catch (err)
{
throw "Failed: " + err;
}
$$
;
CALL DWH.sp_testing();
Are you sure your query is returning data? Also are you sure the target table has data matching your DELETE statement?
The following test works for me using your stored procedure:
select count(*) from citibike_trips where end_station_id=6215;
returns: 14565
Now, I created your stored proc:
CREATE OR REPLACE PROCEDURE sp_testing()
RETURNS string
LANGUAGE javascript
strict
EXECUTE AS owner
AS
$$
try
{
var application_list = `SELECT end_station_id
FROM citibike_trips
WHERE end_station_id=6215 limit 10
`
var query_statement = snowflake.createStatement({sqlText: application_list});
var application_list_result = query_statement.execute();
for (i = 1; i <= query_statement.getRowCount(); i++)
{
application_list_result.next()
end_station_id = application_list_result.getColumnValue(1)
var delete_daily_records = `
DELETE FROM citibike_trips AS target
WHERE target.end_station_id = ${end_station_id}
`
snowflake.execute({sqlText: delete_daily_records});
}
}
catch (err)
{
throw "Failed: " + err;
}
$$
;
Run it:
CALL SP_TESTING();
Shows NULL as result (expected since it is not returning anything).
But then when I check the table again:
select count(*) from citibike_trips where end_station_id=6215;
returns: 0

I am trying Try and Catch Error handling for snowflake stored procedure. in the Stored procedure there are four different statements s

I am trying to do error handling for snowflake stored procedure. The stored procedure has 4 SQL statement inside. I need check errors for all for statement. When error pops, i need to know which Statement error. How to achieve this? Thanks in advance. I appreciate it.
CREATE OR REPLACE PROCEDURE dw.sp_fidctp_audit_trail_load(SYSDATE varchar)
RETURNS STRING
LANGUAGE javascript
AS
'
var sql_command = "truncate table STG.tb_fidctp_audit_trail_input";
var result = snowflake.execute ( {sqlText: sql_command, binds: [SYSDATE]});
sql_command =`COPY INTO STG.tb_fidctp_audit_trail_input (record_id,ORDER_ID,PARENT_ORDER_ID,EVENT_TYPE,EVENT_DATETIME,PRIMARY_STATE,SECONDARY_STATE,CURRENT_SERVICE_ID,CURRENT_EXECUTOR_ID,TRADING_QUANTITY,LIMIT_PRICE,EVENT_TEXT,ORDER_NOTES,REASON_TEXT,ROUTED_ORDER_CODE,ROOT_ORDER_ID,INSTRUMENT_CODE,ORDER_SOURCE,LEAVES,ACCT_ID,CUST_CROSS_ID,ORDER_FLAGS)
from #STG.CTP_STAGE/AUDIT_TRAIL.TOP.`;
sql_command += SYSDATE;
sql_command += ".psv.gz file_format = (FORMAT_NAME = ''STG.CTP_AUDIT_TRAIL_FF'', ERROR_ON_COLUMN_COUNT_MISMATCH = TRUE, encoding = ''iso-8859-1'') ";
try{
result = snowflake.execute (
{sqlText: sql_command, binds: [SYSDATE]}
);
return "Succeeded."; // Return a success/error indicator.
}
catch (err) {
return "Failed: " + err + "ERROR ON COPY STATEMENT"; // Return a success/error indicator.
}
sql_command = `delete from DW.tb_fidctp_audit_trail
where SYSDATE = to_date((:1), ''YYYYMMDD'')`;
result = snowflake.execute ( {sqlText: sql_command, binds: [SYSDATE]});
sql_command = ` insert into DW.tb_fidctp_audit_trail(sysdate, order_id, parent_order_id, root_order_id, event_datetime, instrument_code, event_text,reason_text, current_service_id, current_executor_id, trading_quantity, leaves )
select to_date(left(event_datetime, 8), ''YYYYMMDD''), order_id, parent_order_id, root_order_id, to_timestamp(left(event_datetime, 24), ''YYYYMMDD HH24:MI:SS.FF''), instrument_code, event_text, reason_text, current_service_id, current_executor_id, cast(trading_quantity as NUMBER(18,0)), cast(leaves as NUMBER(18,0))
from STG.tb_fidctp_audit_trail_input
`;
try{
snowflake.execute (
{sqlText: sql_command, binds: [SYSDATE]}
);
return "Succeeded."; // Return a success/error indicator.
}
catch (err) {
return "Failed: " + err + "ERROR ON INSERT STATEMENT"; // Return a success/error indicator.
}
'
;
There are a few things to do here. Let's start with the create header:
CREATE OR REPLACE PROCEDURE dw.sp_fidctp_audit_trail_load(SYSDATE varchar)
RETURNS STRING
LANGUAGE javascript
AS
'
When you use a single quote to enclose the body of a JavaScript procedure, it makes using single quotes in SQL statements inside the body very cumbersome, having to double the single quotes. To avoid that, you can use an alternate string terminator in Snowflake, $$ like this:
CREATE OR REPLACE PROCEDURE dw.sp_fidctp_audit_trail_load(SYSDATE varchar)
RETURNS STRING
LANGUAGE javascript
AS
$$
// Body goes here. There's no longer a need to escape single quotes
$$;
The issue with the return values is in this part:
return "Succeeded."; // Return a success/error indicator.
}
catch (err) {
return "Failed: " + err + "ERROR ON COPY STATEMENT"; // Return a success/error indicator.
}
In JavaScript, when the return statement is used in the main function, it leaves the main function, which terminates the stored procedure. This means if the copy statement succeeds, the procedure will exit and return "Succeeded." If the copy statement fails, it will hit a different return statement and exit with the error message.
Either way, it will terminate execution after that code block.
Take the return statements out of the successful branches of the try/catch blocks except for the last one.
You can just print the statement in the CATCH statement, either by printing the sql_command variable, or using getSqlText() function from the Statement object.
CREATE OR REPLACE PROCEDURE test_catch_error_sp(SYSDATE varchar)
RETURNS STRING
LANGUAGE javascript
AS
$$
var commands = [];
var params = [];
var results = {"success": [], "failed": []};
commands[0] = "create or replace table test1 (a int)";
params[0] = [];
commands[1] = "INSERT INTO test1 values (?)";
params[1] = [SYSDATE];
commands[2] = "drop table test1";
params[2] = [];
var stmt = null;
for (i = 0; i < commands.length; i++) {
try {
stmt = snowflake.createStatement({sqlText: commands[i], binds: params[i]});
var result = stmt.execute();
results['success'].push(stmt.getSqlText());
stmt = null; // reset it
} catch (err) {
// if createStatement failed, then stmt will still be null
results['failed'].push(
"Failed: " + err + ". ERROR ON STATEMENT: '" + stmt ? stmt.getSqlText() : commands[i] + "'"
);
}
}
return JSON.stringify(results);
$$
;
call test_catch_error_sp('2020-01-01');
-- returns:
-- {"success":["create or replace table test1 (a int)","drop table test1"],"failed":["INSERT INTO test1 values (?)"]}

How to assign a sql query result to a variable in stored procedure

I am trying to assign a simple sql query result in stored procedure to a variable which I will use it later in another sql statement. If I execute below statement with out putting in procedure it works but not with stored procedure, I get an error while calling sp , Can someone please help me here ?
Procedure code:
$$
VAR NAME = 'ABC'
SET (COUNT_VALUE) = (SELECT COUNT(*) FROM COUNT_TABLE)
With out Procedure below code works
SET (COUNT_VALUE) = (SELECT COUNT(*) FROM COUNT_TABLE)
select $COUNT_VALUE
Thanks
Here's an example that uses a simple helper function to return a result set from a query. You can read the Snowflake docs to see what the result set API has in it. The getResultSet function returns a variable that has the results of the query. Since the SQL is a count, you need to use rs.next() once to get to the first row and then read the value in the aliased count(*). You could also use rs.getColumnValue(1) to get a column by ordinal position, but I recommend using column names with SQL aliases is necessary.
create or replace procedure COUNT_EXAMPLE()
returns string
language javascript
as
$$
var rowCount = 0;
var sql = "select count(*) as ROW_COUNT from SNOWFLAKE_SAMPLE_DATA.TPCH_SF10000.ORDERS;";
try {
var rs = getResultSet(sql);
if (rs.next()) {
var rowCount = rs.getColumnValue("ROW_COUNT");
} else {
return "Error: Count query failed.";
}
}
catch(err) {
return "Error: " + err.message;
}
return "The table has " + rowCount + " rows.";
//--------------------------- End of main function ---------------------------
function getResultSet(sql){
cmd1 = {sqlText: sql};
stmt = snowflake.createStatement(cmd1);
var rs;
rs = stmt.execute();
return rs;
}
$$;
call COUNT_EXAMPLE();

Snowflake Stored Procedure - how do I retrieve the number of rows created by a CTAS Statement

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

Resources