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);
$$
;
Related
I have created following stored procedure to update recon_dashboard table.
create or replace procedure ca_adhoc_view.sp_count_recon_refresh()
language plpgsql as
$$
declare
f record;
BEGIN
for f in select alvid from ca_adhoc_view.recon_dashboard
loop
raise notice '% alv',f.alv;
execute 'update ca_adhoc_view.recon_dashboard set alv_count =
(select count(*) from ' || f.alv || ')
where id='|| f.id;
end loop;
END;
$$;
It works fine unless there is a null value in the alv, then it throws an error.
I tried to check for null like:
create or replace procedure ca_adhoc_view.sp_count_recon_refresh()
language plpgsql as
$$
declare
f record;
BEGIN
for f in select alv,id from ca_adhoc_view.recon_dashboard
loop
raise notice '% alv',f.alv;
execute 'update ca_adhoc_view.recon_dashboard set alv_count =
(IF '||f.alv||' is not null
then (select count(*) from ' || f.alv || ')
END IF;)
where id='|| f.id;
end loop;
END;
$$;
But it throws error when I try to call the SP.
Error: SQL Error [42601]: ERROR: syntax error at or near "ca_view"
Where: PL/pgSQL function "sp_count_recon_refresh" line 7 at execute statement
In error "ca_view" is a recod. alv column has values like 'ca_view.table1', 'ca_view.table2' and so on.
This probably fixes your problems:
CREATE OR REPLACE PROCEDURE ca_adhoc_view.sp_count_recon_refresh()
LANGUAGE plpgsql AS
$func$
DECLARE
f record;
BEGIN
FOR f IN
SELECT alv, id FROM ca_adhoc_view.recon_dashboard
LOOP
RAISE NOTICE '% alv', f.alv;
IF f.alv IS NOT NULL THEN
EXECUTE format(
'UPDATE ca_adhoc_view.recon_dashboard
SET alv_count = (SELECT count(*) FROM %I)
WHERE id = $1', f.alv)
USING f.id;
END IF;
END LOOP;
END
$func$;
"Problems" (plural). Besides defending against a NULL value for the table name with proper syntax, this also prevents SQL injection. Your original is wide open there. Related:
PostgreSQL - Writing dynamic sql in stored procedure that returns a result set
Table name as a PostgreSQL function parameter
That said, it would be more efficient to run a single UPDATE instead of updating one row per loop iteration. Consider building and executing a single statement.
I am trying to parse JSON value and getting Syntax Error in stored procedure.
This SELECT statement works fine:
SELECT
parse_json ('{"fName":"Pink","lName":"Panther"}') AS json_data,
json_data:fName::string AS first_name,
json_data:lName::string AS last_name;
While trying same thing in stored procedure, I am getting a syntax error:
CREATE OR REPLACE PROCEDURE extract_json(input_json varchar)
RETURNS TABLE (res varchar)
LANGUAGE SQL
AS
$$
DECLARE
qry string;
res resultset;
BEGIN
qry := 'SELECT parse_json('||:input_json||') AS json_data::sting, json_data:fName::string';
res := (execute immediate qry);
return table(res);
END;
$$
;
CALL extract_json('{"fName":"Pink","lName":"Panther"}');
Expected out, 2 columns only:
FIRST_NAME LAST_NAME
Pink Panther
Any help is appreciated.
Thanks
The are few issues with the code:
a) resulset contains two column so (res varchar) will not work
b) alias json_data::sting cannot be casted
c) missing ' around input_json, ideally it should be bind parameter
CREATE OR REPLACE PROCEDURE extract_json(input_json varchar)
RETURNS TABLE (col VARIANT, res varchar)
LANGUAGE SQL
AS
$$
DECLARE
qry string;
res resultset;
BEGIN
qry := 'SELECT parse_json('''||:input_json||''') AS json_data, json_data:fName::string';
res := (execute immediate qry);
return table(res);
END;
$$
;
CALL extract_json('{"fName":"Pink","lName":"Panther"}');
Output:
Expected out, 2 columns only:
CREATE OR REPLACE PROCEDURE extract_json(input_json varchar)
RETURNS TABLE (firstName VARCHAR, lastName varchar)
LANGUAGE SQL
AS
$$
DECLARE
qry string;
res resultset;
BEGIN
qry := 'SELECT json_data:fName::string, json_data:lName::string FROM (SELECT parse_json('''||:input_json||''') AS json_data)';
res := (execute immediate qry);
return table(res);
END;
$$;
CALL extract_json('{"fName":"Pink","lName":"Panther"}');
Output:
I am trying to call a procedure inside a procedure but this gives me an error like:
Uncaught exception of type 'STATEMENT_ERROR' on line 19 at position 2 : This session warehouse WH_STD_EDWQA_ANALYST no longer exists.
My parent procedure construct is like creating a warehouse & the child procedure is to populate a metadata table(custom) by use of table(result_scan(last_query_id())).
Parent procedure construct:
create or replace procedure wh_resource_govern(type varchar, env varchar, ..., varchar)
returns varchar not null
language sql
as
$$
declare
wh_name varchar;
wh_setup varchar;
lv_acct_name varchar;
begin
wh_name := 'WH_' || type || '_' || env || '_' || team;
wh_setup := 'CREATE OR REPLACE WAREHOUSE' || ' ' || wh_name || ' ' || 'WITH' || ' '
|| 'WAREHOUSE_SIZE = ' || v_wh_size || ' '
...,
|| 'COMMENT= '|| '"' || v_created_by || '"' ;
execute immediate wh_setup;
commit;
call load_all_warehouse_metadata('a', 'b', 'c'); ----> This is where it is getting stuck.
end;
$$
;
Child procedure construct is given as below:
create or replace procedure load_all_warehouse_metadata(wh_type varchar, wh_env varchar, wh_team varchar)
returns varchar not null
language sql
as
$$
declare
lv_acct_name varchar;
begin
select current_account() into lv_acct_name;
show warehouses;
insert into ALL_WAREHOUSE_METADATA (account_name, warehouse_type, .., .., )
select :lv_acct_name, :wh_type, :wh_env, :wh_team, "name", ..., ...,
from table(result_scan(last_query_id()));
end;
$$
;
Any inputs on how to address this would be really helpful.
Creating a warehouse immediately makes it the current warehouse for the session, example:
create or replace warehouse FOO;
select current_warehouse(); -- FOO
drop warehouse FOO();
select current_warehouse(); -- NULL
When you run execute immediate wh_setup; in the first SP, it's setting the session's warehouse to the one you just created. Calling a child SP using owner's rights (default) from a warehouse that isn't the one that started the SP is causing context problems for the warehouse.
You can reproduce this error as follows:
create or replace procedure SP1()
returns varchar not null
language sql
--execute as caller
as
$$
declare
currentWarehouse varchar;
begin
select current_warehouse() into currentWarehouse;
create or replace warehouse FOO;
call SP2();
return currentWarehouse;
end;
$$;
create or replace procedure SP2()
returns varchar not null
language sql
--execute as caller
as
$$
declare
currentWarehouse varchar;
begin
select current_warehouse() into currentWarehouse;
create or replace temp table FOO(s string);
insert into FOO(S) values ('Bar');
return 'Done';
end;
$$;
call SP1();
You can fix this code sample immediately by uncommenting the two commented-out ownership options for SP1 and SP2:
--execute as caller (Remove the comment markers and recreate both SPs.)
You can also run a SQL command to use warehouse <wh_name> in your SP(s), but you must run the SP as caller in order to change warehouse context this way.
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:"
I need to dynamically populate an Oracle cursor (Oracle 10g). The SQL statement changes, based off of the input value to pull from different tables, and columns. What I don't want to do is have to maintain a temporary table that I truncate and load everytime the sproc is executed. Here is what I am currently doing, but if there is another alternative I would appreciate the help:
Stored Procedure
PROCEDURE Get_Type_One_Polygon_Values(in_role VARCHAR2, rc_generic OUT SYS_REFCURSOR) as
BEGIN
execute immediate 'truncate table teamchk.temp_type_one_roles';
execute immediate 'INSERT INTO TEAMCHK.TEMP_TYPE_ONE_ROLES ' ||
'SELECT ' || in_role || '_POLY_ID, ' || in_role || '_POLY_NAME ' ||
'FROM TEAMCHK.' || in_role;
open rc_generic for
select * from teamchk.temp_type_one_roles;
END;
Temp Table
CREATE TABLE TEAMCHK.TEMP_TYPE_ONE_ROLES
(
ROLE_ID NUMERIC(38,0),
ROLE_NAME VARCHAR2(75)
);
That's easy, you can use dynamic cursors...
create or replace PROCEDURE Get_Type_One_Polygon_Values
(in_role VARCHAR2, rc_generic OUT SYS_REFCURSOR) as
sql varchar2(100);
BEGIN
sql :='SELECT ' || in_role || '_POLY_ID, '
|| in_role || '_POLY_NAME '
|| 'FROM TEAMCHK.' || in_role;
open rc_generic for sql;
END;
It may be beneficial to use column aliases POLY_ID and POLY_NAME to unify them all in the refcursor.