Dynamic SQL in a Snowflake SQL Stored Procedure

Dynamic SQL in a Snowflake SQL Stored Procedure

An example of commenting schemas in a specific database:

Preparing setup:

CREATE DATABASE TEST3;
CREATE SCHEMA TEST1;
CREATE SCHEMA TEST2;

SELECT CATALOG_NAME, SCHEMA_NAME, COMMENT
FROM TEST3.INFORMATION_SCHEMA.SCHEMATA;

Sample Image

Procedure:

CREATE OR REPLACE PROCEDURE test_proc(DB_NAME STRING)
RETURNS STRING
LANGUAGE SQL
AS
$$
DECLARE
TABLE_NAME STRING;
QUERY STRING;
OUTPUT STRING DEFAULT '';
c1 CURSOR FOR SELECT SCHEMA_NAME FROM TABLE(?)
WHERE SCHEMA_NAME != 'INFORMATION_SCHEMA';
BEGIN
TABLE_NAME := CONCAT(DB_NAME, '.INFORMATION_SCHEMA.SCHEMATA');
OPEN c1 USING (TABLE_NAME);

FOR rec IN c1 DO
QUERY := 'COMMENT ON SCHEMA ' || DB_NAME || '.' || rec.SCHEMA_NAME
|| ' IS ''test_comment'';';
OUTPUT := OUTPUT || QUERY;
EXECUTE IMMEDIATE :QUERY;
END FOR;

RETURN :OUTPUT;
END;
$$;

Call:

CALL test_proc('TEST3');

Output:

Sample Image

Sample Image

can we use dynamic sql in snowflake scripting?

Is there a way to do this sort of things in Snowflake SQL (as opposed to Stored procedures using javascript)?

Yes, it is possible to build arbitrary query and execute it using with Snowflake Scripting. EXECUTE IMMEDIATE:

Executes a string that contains a SQL statement or a Snowflake Scripting statement.

EXECUTE IMMEDIATE '<string_literal>'
[ USING (bind_variable_1 [, bind_variable_2 ...] ) ] ;

Related: Snowflake dynamic SQL: Equivalent of DBMS_SQL/sp_executesql, Dynamic SQL in a Snowflake SQL Stored Procedure

Snowflake SQL stored procedure and save value from query with dynamic SQL

This is what I got to work using slightly different syntax for using variables.
Syntax taken from here

create or replace procedure myprocedure(DBNAME varchar(16777216))
returns int
language sql
as
$$
DECLARE
excludeCount int;
fullyQualifiedProceduresTable varchar(16777216);

BEGIN
SELECT CONCAT(:DBNAME, '.INFORMATION_SCHEMA.PROCEDURES') into :fullyQualifiedProceduresTable;
SELECT count(*) into :excludeCount from IDENTIFIER(:fullyQualifiedProceduresTable) WHERE PROCEDURE_OWNER = '<role>';

IF (excludeCount > 0) THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
END;
$$;

Snowflake Stored procedure with dynamic SQL statement Error

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();

Snowflake create views dynamically via stored procedure

Cleaned up the procedure code a bit.
Replace the needed logic inside the CURSOR FOR LOOP

CREATE OR REPLACE PROCEDURE VIEW_BUILDER()
returns varchar
language sql
AS
$$
DECLARE
qry_head varchar := '';
qry_stmt varchar := '';
sql_stmt varchar := '';
BEGIN
DROP TABLE IF EXISTS SYS_INFO_OUT;
CREATE or replace TEMPORARY TABLE SYS_INFO_OUT AS
SELECT tbl.TABLE_NAME,
col.COLUMN_NAME,
col.DATA_TYPE,
COALESCE(col.CHARACTER_MAXIMUM_LENGTH,col.CHARACTER_OCTET_LENGTH,col.NUMERIC_PRECISION ,col.DATETIME_PRECISION) AS COL_LENGTH,
col.ORDINAL_POSITION,
col.IS_NULLABLE,
CASE WHEN nvl(col.NUMERIC_SCALE,0) = 0 THEN 0 ELSE 1 END AS is_decimal
FROM INFORMATION_SCHEMA.TABLES AS tbl
INNER JOIN INFORMATION_SCHEMA.COLUMNS AS col ON tbl.TABLE_NAME = col.TABLE_NAME AND tbl.TABLE_SCHEMA = col.TABLE_SCHEMA
WHERE tbl.TABLE_SCHEMA ='SOME_SCHMEA' AND tbl.TABLE_NAME ='SOME_TABLE'
ORDER BY tbl.TABLE_NAME,
col.ORDINAL_POSITION ASC;
let c1 cursor for SELECT * FROM SYS_INFO_OUT ;
for record in c1 do
qry_head:='x'; --- replace with code as needed
end for;
sql_stmt := sql_stmt ||'* FROM PUBLIC.TABLEA';
qry_head := 'CREATE OR REPLACE VIEW PUBLIC.VDF_SOME_TABLE AS SELECT ';
EXECUTE IMMEDIATE (qry_head||sql_stmt);
RETURN 'done';
END;
$$
;

call VIEW_BUILDER();












VIEW_BUILDER
done

Snowflake Dynamic query to run the sql from another table

The main problem is the try block was in the wrong place. There were some other errors. For example, when comparing values in JavaScript, use == or ===. Using = is only for assignments and evaluates incorrectly inside if expressions.

create or replace transient table T1 (ID int, SQL_TEXT string, SQL_NAME string);

insert into T1 (ID, SQL_TEXT, SQL_NAME) values
(1,'Select current_date();', 'Test1'),
(2,'Select current_timestamps();', 'Test2'),
(3,'Select * from foobar10384 where t1 > 1000;', 'Test3'),
(4,'select * from t2 where date < current_date();', 'Test4');


create or replace procedure TestQuery (TABLE_NAME string, SQL_TEXT string, SQL_NAME string)
returns string
not null
language javascript
as
$$
var op = "";
// Dynamically compose the SQL statement to execute.
var sqlCommand = `select ${SQL_TEXT}, ${SQL_NAME} from ${TABLE_NAME}`;
// Prepare statement.
var stmt = snowflake.createStatement(
{
sqlText: sqlCommand
}
);
// Execute Statement

var res = stmt.execute();

while (res.next()) {
v_col_name = res.getColumnValue("SQL_TEXT");
sqlcommand2 = "explain using tabular " + v_col_name
var stmt2 = snowflake.createStatement(
{
sqlText: sqlcommand2
}
);
try {
var res2 = stmt2.execute();
} catch (err) {
if (op != "") op += ",";
op += res.getColumnValue("SQL_NAME");
}
}
if (op == "") {
return "all success"
}
else {
return op}
$$;

call testquery('T1', 'SQL_TEXT', 'SQL_NAME');

Edit with a final thought: Since this is going through the work of identifying bad SQL, it should return the reason it's bad. This refactor returns an array. The array will be empty if there are no SQL errors. It will be filled with one or more objects containing the name of the SQL with an error and the error itself:

 create or replace procedure TestQuery (TABLE_NAME string, SQL_TEXT string, SQL_NAME string)
returns array
not null
language javascript
as
$$
var op = [];
var err = {};
// Dynamically compose the SQL statement to execute.
var sqlCommand = `select ${SQL_TEXT}, ${SQL_NAME} from ${TABLE_NAME}`;
// Prepare statement.
var stmt = snowflake.createStatement(
{
sqlText: sqlCommand
}
);
// Execute Statement

var res = stmt.execute();

while (res.next()) {
v_col_name = res.getColumnValue("SQL_TEXT");
sqlcommand2 = "explain using tabular " + v_col_name
var stmt2 = snowflake.createStatement(
{
sqlText: sqlcommand2
}
);
try {
var res2 = stmt2.execute();
} catch (e) {
err = {};
err.SQL_NAME = res.getColumnValue("SQL_NAME");
err.SQL_ERROR = e.message;
op.push(err);
}
}
return op
$$;

Output:

[
{
"SQL_ERROR": "SQL compilation error:\nUnknown function CURRENT_TIMESTAMPS",
"SQL_NAME": "Test2"
},
{
"SQL_ERROR": "SQL compilation error:\nObject 'FOOBAR10384' does not exist or not authorized.",
"SQL_NAME": "Test3"
},
{
"SQL_ERROR": "SQL compilation error: error line 1 at position 45\ninvalid identifier 'DATE'",
"SQL_NAME": "Test4"
}
]

Snowflake dynamic SQL: Equivalent of DBMS_SQL/sp_executesql

It is possible using Snowflake Scripting:

DROP TABLE IF EXISTS wide5;

-- to be run with Snowsight
DECLARE
SQL STRING;
BEGIN
SELECT create_table_sql
INTO :SQL
FROM (
SELECT LISTAGG(REPLACE(CHAR(13) || ',a<index> INT DEFAULT UNIFORM(1, 10000, RANDOM())'
, '<index>', seq8()+1),'')
WITHIN GROUP(ORDER BY seq8()) AS column_list

,REPLACE(REPLACE(
'CREATE OR REPLACE TABLE <table_name>(id INT <column_list>);'
,'<table_name>', 'wide5')
,'<column_list>', column_list) AS create_table_sql
FROM TABLE(GENERATOR(rowcount => 5))
);

EXECUTE IMMEDIATE :SQL;
END;

SELECT * FROM wide5;
-- ID A1 A2 A3 A4 A5

Is it possible to generate dynamic sql to return rows with various columns in snowflake

Given this input:

create or replace table t as
select parse_json($1) my_json
from values
('{ "FruitShape":"Round", "FruitSize":55 } '),
('{ "FruitShape":"Square" } '),
('{ "FruitShape":"Oblong", "FruitSize":22, "FruitColor":"Chartreuse" } ')
;

This query will generate the dynanmic SQL:

select 'select ' 
|| (select listagg(distinct 'my_json:'||key::text, ',') from t, lateral flatten(input=>t.my_json, mode=>'OBJECT'))
|| ' from t;';

Generated SQL and it's output:

select my_json:FruitShape, my_json:FruitSize, my_json:FruitColor from t;

MY_JSON:FRUITSHAPE | MY_JSON:FRUITSIZE | MY_JSON:FRUITCOLOR
-------------------+-------------------+-------------------
"Round" | 55 | NULL
"Square" | NULL | NULL
"Oblong" | 22 | "Chartreuse"

This stored procedure will execute dynamic SQL without cut-and-paste:

create or replace procedure p()
returns string
language javascript
strict
execute as caller
as
$$
const statement1 = `
select 'select '
|| (select listagg(distinct 'my_json:'||key::text, ', ') from t, lateral flatten(input=>t.my_json, mode=>'OBJECT'))
|| ' from t'
`
const rs1 = snowflake.execute ({sqlText: statement1})
rs1.next()
const statement2 = rs1.getColumnValue(1)
const rs2 = snowflake.execute ({sqlText: statement2})
return 'SUCCESS'
$$
;

Then you can call the stored procedure and collect the results:

call p();
select * from table(result_scan(-2))

You mentioned constraining the output depending on some metadata. You can do that in the dynamic SQL, for example by filtering the distinct list of fields.

Credits to davidgarrison for the result_scan() technique!

Hope that's helpful.



Related Topics



Leave a reply



Submit