Snowflake Javascript executing more than one sql
Multiple SQL statements in a single API call are not supported; use one API call per statement instead.
Used stmt.executemany() instead of execute() but no luck, another error "executemany() is not a function"
Please help
CREATE OR REPLACE PROCEDURE GrantSchemaTablePermissions ()
returns varchar
language javascript
AS
$$
var table_control = " SELECT DISTINCT TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA LIKE 'ABCD%' ";
var sql_statement = snowflake.createStatement({sqlText: table_control});
var resultSet = sql_statement.execute();
while (resultSet.next()) {
var key_column_name = resultSet.getColumnValue(1);
var InsertSelect = "USE ROLE OPS; GRANT OWNERSHIP on all tables in schema ABCD." + resultSet.getColumnValue(1) + " TO ROLE LOADER;"
//return InsertSelect
var stmt = snowflake.createStatement(
{
sqlText: InsertSelect
}
);
var res = stmt.execute(); //tried executemany() but no luck
//Cursor.executemany()
return InsertSelect
//return stmt.getSqlText();
}
$$
;
CALL GrantSchemaTablePermissions();
If a function doesn't exist such as executemany, you can write it.
function executemany(statements) {
let statements = statements.split(';');
for (let i = 0; i < statements.length; i++) {
if (statements[i].trim().length > 0)
try {
getResultSet(statements[i]);
} catch (err){
return {Error: err.message, statement: statements[i]};
}
}
}
You can add that to the very bottom of your SP and try running it.
I just wrote that for someone who wanted to put a large section of SQL statements into the body of a stored procedure and run them one at a time.
A couple of notes: 1) This does NOT check for semicolons inside of single quotes. It assumes that a semicolon separates one statement from another. 2) You can use backticks ` to open and close the string. This will let you put the statements in a multi-line block. When I did this with ~20 statements with the semicolons on a line by themselves between lines (the user's preference), it confused the web UI's parser between what was the body of the SP and what was outside it. I fixed that by escaping the single line semicolons with a backslash like this:
GRANT ROLE IDENTIFIER($ROLENAME) TO USER IDENTIFIER($USERNAME)
\;
GRANT ROLE IDENTIFIER($ROLENAME) TO ROLE ACCOUNTADMIN
\;
Related
I am using Snowflake stored procedures to try and log errors as they occur. The current stored proc has an if/else statement, but I'm just trying to get one section working for now since the code is the more or less the same with variations to the SQL statements.
create or replace Procedure PLog(
PName varchar,
CName varchar,
PType varchar)
returns varchar
not null
language javascript
AS
$$
var cyc_id_sql = `SELECT case when count(CycleId) > 0 then max(CycleID) + 1 else 1 end as CycleId from SCHEMA.TABLE1 where CycleName='${CName}'`;
var cycle_id_create = snowflake.createStatement({ sqlText: cyc_id_sql});
var cycleid = cycle_id_create.execute();
var p_id_sql = `Select case when count(ProcessId) > 0 then max(ProcessID) + 1 else 1 end as ProcessId from SCHEMA.TABLE2 where ProcessName='${PName}'`;
var p_id_create = snowflake.createStatement({ sqlText: p_id_sql});
var processid = p_id_create.execute();
var insertValuesText = `INSERT INTO SCHEMA.TABLE
(
ProcessLogId
,CycleId
,ProcessId
,CycleName
,ProcessName
,ProcessType
,ProcessStatus
,StartTime)
VALUES
(default,${cycleid},${processid},'${value3}','${value4}','${value5}','Started',current_timestamp )`;
var insertValues = snowflake.createStatement({ sqlText: insertValuesText});
insertValues.execute();
$$
When I call this I get the error:
SQL compilation error: syntax error line 12 at position 28 unexpected 'Object'. syntax error line 12 at position 44 unexpected 'Object'. At Statement.execute, line 22 position 13
I think the issue is that the cycle and process statements create a Javascript object instead of the desired value. The desired value being the unique ID. When I run the SQL separately with an empty database I get 1, which is expected so I know the SELECT statements work. When I run and then call:
create or replace Procedure PLog(
PName varchar,
CName varchar,
PType varchar)
returns varchar
not null
language javascript
AS
$$
var cyc_id_sql = `SELECT case when count(CycleId) > 0 then max(CycleID) + 1 else 1 end as CycleId from SCHEMA.TABLE1 where CycleName='${CName}'`;
var cycle_id_create = snowflake.createStatement({ sqlText: cyc_id_sql});
var cycleid = cycle_id_create.execute();
return cycleid
$$
I get [object Object]. I'm not that familiar with JavaScript so any help would be appreciated. I think the issue is that my variables generate instances of an object instead of a usable value for the SQL statement, I just don't know how to fix the issue. Thank you!
when you execute a statement var processid = p_id_create.execute(); processid is the result set, not the actual value you want. you need to fetch it
so here is some example from the doc's
CREATE OR REPLACE PROCEDURE right_bind(TIMESTAMP_VALUE VARCHAR)
RETURNS BOOLEAN
LANGUAGE JAVASCRIPT
AS
$$
var cmd = "SELECT CURRENT_DATE() > TO_TIMESTAMP(:1, 'YYYY-MM-DD HH24:MI:SS')";
var stmt = snowflake.createStatement(
{
sqlText: cmd,
binds: [TIMESTAMP_VALUE]
}
);
var result1 = stmt.execute();
result1.next();
return result1.getColumnValue(1);
$$
;
so your small example should be:
create or replace Procedure PLog(
PName varchar,
CName varchar,
PType varchar)
returns varchar
not null
language javascript
AS
$$
var cyc_id_sql = `SELECT case when count(CycleId) > 0 then max(CycleID) + 1 else 1 end as CycleId from SCHEMA.TABLE1 where CycleName='${CName}'`;
var cycle_id_create = snowflake.createStatement({ sqlText: cyc_id_sql});
var result1 = cycle_id_create.execute();
result1.next();
var cycleid result1.getColumnValue(1);
return cycleid
$$
You are correct that it's returning a JavaScript object. As Simeon notes, this is returning a resultSet. If you're expecting a query to return a scalar (single row with a single column) you can use inline code to get it. However, it will not be modular and harder to read than modular code. This is a great example of where a helper function helps.
For example:
create or replace procedure TEST()
returns string
language javascript
as
$$
var my_scalar = getScalar("select C_NAME from SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER limit 1");
return my_scalar;
function getScalar(sql){
cmd = {sqlText: sql};
stmt = snowflake.createStatement(cmd);
var rs;
rs = stmt.execute();
rs.next();
return rs.getColumnValue(1);
}
$$;
You can then use the getScalar helper function to collect scalar values you need. If you're only doing it once it won't matter too much. If you're collecting a few or dozens, then it will keep the code modular, compact, and more readable.
I've been trying for hours to make the following Google Apps Script work. What it needs to do, is send emails (from an html-template) to anyone that:
has a complete Event Schedule (which is completed if they have been
assigned to at least 4 events, which is counted in column Q);
has NOT been sent an email earlier (which is kept track of in column
R);
The script keeps track of errors in column S, i.e. if there's no email address provided.
It appears it only works:
if I comment out
data = data.filter(function(r){ return r[17] == true & r[16] > 3});
or if I comment out
ws.getRange("S3:S" + ws.getLastRow()).setValues(errors);
ws.getRange("R3:R" + ws.getLastRow()).setValues(mailSucces);
How can I get this script to work properly?
A copy of the Google Sheet I'm referring to is this one:
https://docs.google.com/spreadsheets/d/1sbOlvLVVfiQMWxNZmtCLuizci2cQB9Kfd8tYz64gjP0/edit?usp=sharing
This is my code so far:
function SendEmail(){
var voornaam = 3;
var achternaam = 4;
var email = 5;
var event1 = 9;
var event2 = 10;
var event3 = 11;
var event4 = 12;
var event5 = 13;
var event6 = 14;
var event7 = 15;
var emailTemp = HtmlService.createTemplateFromFile("email");
var ws = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Events Day 1");
var datum = ws.getRange(1,3).getValue();
var spreker = ws.getRange(1,6).getValue();
var data = ws.getRange("A3:R" + ws.getLastRow()).getValues();
data = data.filter(function(r){ return r[17] == false && r[16] > 3}); //Either this needs to be commented out...
let errors = [];
let mailSucces = [];
data.forEach(function(row){
try{
emailTemp.voornaam = row[voornaam];
emailTemp.email = row[email];
emailTemp.datum = datum;
emailTemp.spreker = spreker;
emailTemp.event1 = row[event1];
emailTemp.event2 = row[event2];
emailTemp.event3 = row[event3];
emailTemp.event4 = row[event4];
emailTemp.event5 = row[event5];
emailTemp.event6 = row[event6];
emailTemp.event7 = row[event7];
var htmlMessage = emailTemp.evaluate().getContent();
GmailApp.sendEmail(
row[email],
"Here you go! Your personal schedule for the event of " + datum,
"Your emailprogramm doesn't support html.",
{
name: "Event Organisation Team", htmlBody: htmlMessage, replyTo: "info#fakeemail.com"
});
errors.push([""]);
mailSucces.push(["TRUE"]);
}
catch(err){
errors.push(["Error: no message sent."]);
mailSucces.push(["False"]);
}
}); //close forEach
ws.getRange("S3:S" + ws.getLastRow()).setValues(errors); //or this and the next line need to be commented out.
ws.getRange("R3:R" + ws.getLastRow()).setValues(mailSucces);
}
Edit I have been trying and thinking en trying... but still haven't found out how to make it work. But I also got understanding of why it's not working; I just don't know how to get it fixed.
Let me elaborate on the problem a bit more:
The problem is, that within the forEach loop the range is a filtered variant of the data, pulled from the spreadsheet with getValues. Therefore, writing data back with ws.getRange("R3:R" + ws.getLastRow()).setValues(mailSucces); results in mismatched checkmarks in te spreadsheet.
So, somehow I need to put the range of the previous used filter data = data.filter(function(r){ return r[17] == false & r[16] > 3}); in a variable...? I guess?
Furthermore, I don't think it's wise to use setValue within the loop, because (from what I understand from my searching on the topic) this results in a slow script, because every loop the script makes an API call to write in the spreadsheet. Hence the errors.push and mailSucces.push, and my attempt to do a setValue at the end, after the loop is finished.
Can someone help me to finish this problem?
The problem is different size of the range you write to and data you are writing in.
Try replacing:
ws.getRange("S3:S" + ws.getLastRow()).setValues(errors);
ws.getRange("R3:R" + ws.getLastRow()).setValues(mailSucces);
With:
ws.getRange(3, 19, errors.length, 1).setValues(errors);
ws.getRange(3, 18, mailSucces.length, 1).setValues(mailSucces);
You should use this variation of getRange
https://developers.google.com/apps-script/reference/spreadsheet/sheet#getrangerow,-column,-numrows,-numcolumns
Your data has non-fixed number of rows and fixed number of columns (1). In general case your data will be matrix of X rows and Y columns. For that purpose you can make it completely dynamic:
sheet.getRange(startRow, startColumn, data.length, data[0].length)
Just make sure data.length is > 0 before you do this, otherwise data[0].length will break.
Edit:
I started writing a comment but it got too long. There are couple of things that may go wrong with sending emails. First thing I noticed is that you use & in filter, but in AppsScript/JavaScript/C-like-languages, you should use && for logical AND. Now the email: you only detect the code break with the catch block. At this point you don't know why the code breaks it could be anything. With GmailApp I recommend you to use createDraft while developing, then when all ok replace it with sendEmail for the final version, both functions have the exact same parameters, thank you Google devs ;-).
To find out the exact problem you should get the error message on break and display it. err.stack should tell you pretty much everything:
catch(err){
Logger.log(err.stack); // Added
errors.push(["Error: no message sent."]);
mailSucces.push(["False"]);
}
Run the sendEmail function from the code editor and you should see the Log for each catch(err) pass.
So, I never ever programmed JavaScript and never did anything with Google Script before either. I have a fairly good understanding of Visual Basic and macros in Excel and Word. Trying to make a fairly basic program: Plow through a list of variables in a spreadsheet, make a new sheet for each value, insert a formula in this new sheet, cell (1,1).
Debug accepts my program, no issues - however, nothing at all is happening when I run the program:
function kraft() {
var rightHere =
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange("A1:A131");
var loopy;
var goshDarn = "";
for (loopy = 1; loopy < 132; loopy++) {
celly = rightHere.getCell(loopy,1);
vaerdi = celly.getValue();
fed = celly.getTextStyle();
console.log(vaerdi & " - " & fed);
if (vaerdi != "" && fed.isBold == false) {
SpreadsheetApp.getActiveSpreadsheet().insertSheet(vaerdi);
var thisOne = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(vaerdi);
thisOne.deleteRows(500,500);
thisOne.deleteColumns(5, 23);
thisOne.getRange(1,1).setFormula("=ArrayFormula(FILTER('Individuelle varer'!A16:D30015,'Individuelle varer'!A16:A30015=" & Char(34) & vaerdi & Char(34) & ")))");
}
}
}
activeSheet could be called by name, so could activeSpreadsheet, I guess. But range A1:A131 has a ton of variables - some times there are empty lines and new headers (new headers are bold). But basically I want around 120 new sheets to appear in my spreadsheet, named like the lines here. But nothing happens. I tried to throw in a log thingy, but I cannot read those values anywhere.
I must be missing the most total basic thing of how to get script connected to a spreadsheet, I assume...
EDIT: I have tried to update code according to tips from here and other places, and it still does a wonderful nothing, but now looks like this:
function kraft() {
var rightHere = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange("A1:A131");
var loopy;
var goshDarn = "";
for (loopy = 1; loopy < 132; loopy++) {
celly = rightHere.getCell(loopy,1);
vaerdi = celly.getValue();
fed = celly.getFontWeight();
console.log(vaerdi & " - " & fed);
if (vaerdi != "" && fed.isBold == false) {
SpreadsheetApp.getActiveSpreadsheet().insertSheet(vaerdi);
var thisOne = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(vaerdi);
thisOne.deleteRows(500,500);
thisOne.deleteColumns(5, 23);
thisOne.getRange(1,1).setFormula("=ArrayFormula(FILTER('Individuelle varer'!A16:D30015,'Individuelle varer'!A16:A30015=" + "\"" + vaerdi + "\"" + ")))");
}
}
}
EDIT2: Thanks to exactly the advice I needed, the problem is now solved, with this code:
function kraft() {
var rightHere = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange("A1:A131");
var loopy;
for (loopy = 1; loopy < 132; loopy++) {
celly = rightHere.getCell(loopy,1);
vaerdi = celly.getValue();
fed = celly.getFontWeight()
console.log(vaerdi & " - " & fed);
if (vaerdi != "" && fed != "bold") {
SpreadsheetApp.getActiveSpreadsheet().insertSheet(vaerdi);
var thisOne = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(vaerdi);
thisOne.deleteRows(500,499);
thisOne.deleteColumns(5, 20);
thisOne.getRange(1,1).setFormula("=ArrayFormula(FILTER('Individuelle varer'!A16:D30015;'Individuelle varer'!A16:A30015=" + "\"" + vaerdi + "\"" + "))");
}
}
}
There are multiple issues with your script, but the main one is that you never actually call the isBold() function in your 'if' statement.
if (value && format.isBold() == false) {
//do something
}
Because you omitted the parentheses in 'fed.isBold', the expression never evaluates to 'true'. 'isBold' (without the parentheses) is of type Object as it's a function.
There are other issues that prevent the script from running properly:
Not using the 'var' keyword to declare variables and polluting the global scope. As a result, all variables you declare within your 'for' loop are not private to your function. Instead, they are attached to the global object and are accessible outside the function. https://prntscr.com/kjd8s5
Not using the built-in debugger. Running the function is not debugging. You should set the breakpoints and click the debug button to execute your function step-by-step and examine all values as it's being executed.
Deleting the non-existent columns. When you create the new sheet, you call the deleteColums(). There are 26 columns in total. The 1st parameter is the starting column while the 2nd one specifies how many columns must be deleted. Starting from column 5 and telling the script to remove 23 columns will throw an exception. Always refer to the documentation to avoid such errors.
console.log doesn't exist within the context of the Script Editor. You are NOT executing the scripts inside your browser, so Browser object model is not available. Use Logger.log(). Again, this is detailed in the documentation.
Your formula is not formatted properly.
JS is a dynamically typed language that's not easy to get used to. If you don't do at least some research prior to writing code, you'll be in for a lot of pain.
I'm trouble shooting this application that I ave had no hand in creating, but I get to fix. Anywho, I'm finding that I am unable to query the postgres db for any variable that has an _. Now before you say use escape characters, I've already been there, done that, and got the t-shirt. From what I could tell, the application uses escape-string-regexp module to alter the string, for example => "some_random#email.com" => "some_random#email\.com" I added my own module to single out the _ and now the output is => "some\\_random#email\.com". Note the two escapes for the underscore as prescribed by the mass forums that I have scaled. I even built a tool to test out in python and that works. Slightly different as I use => "(E'some\\_random#email\\.com')". Tried that with javascript as well and still no dice. What say all ye? Oh, this shoots out via expressjs from what I can tell. I'm not a javascript guy.
Found the solution. I and another team mate went back and revisited the method I created and changed the " to '. Next thing you know, it started working.
exports.stringChanger = function(word){
//console.log(word)
var metas = ["_"];
var guestPool = word.split("");
//console.log(guestPool)
for(i in range(guestPool.length)){
for(j in range(metas.length)){
if(guestPool[i] === metas[j]){
guestPool[i] = "\\" + guestPool[i];
}
}
}
var guest = guestPool.join("");
return guest;
//console.log(guest)
};
to
exports.stringChanger = function(word){
//console.log(word)
var metas = ['_'];
var guestPool = word.split("");
//console.log(guestPool)
for(i in range(guestPool.length)){
for(j in range(metas.length)){
if(guestPool[i] === metas[j]){
guestPool[i] = '\\' + guestPool[i];
}
}
}
var guest = guestPool.join("");
return guest;
//console.log(guest)
};
I'm working on my final project of the Winter 2017 quarter to demonstrate how to use Regular Expressions in both C# and JavaScript code behind pages. I've got the C# version of my demonstration program done, but the JavaScript version is making me pull what little hair I have left on my head out (no small achievement since I got a fresh buzz cut this morning!). The problem involves not getting any output after applying a Regular Expression in a While loop to get each instance of the expression and printing it out.
On my HTML page I have an input textarea, seven radio buttons, an output textarea, and two buttons underneath (one button is to move the output text to the input area to perform multiple iterations of applying expressions, and the other button to clear all textareas for starting from scratch). Each radio button links to a function that applies a regular expression to the text in the input area. Five of my seven functions work; the sixth is the one I can't figure out, and the seventh is essentially the same but with a slightly different RegEx pattern, so if I fix the sixth function, the seventh function will be a snap.
(I tried to insert/upload a JPG of the front end, but the photo upload doesn't seem to be working. Hopefully you get the drift of what I've set up.)
Here are my problem children from my JS code behind:
// RegEx_Demo_JS.js - code behind for RegEx_Demo_JS
var inputString; // Global variable for the input from the input text box.
var pattern; // Global variable for the regular expression.
var result; // Global variable for the result of applying the regular expression to the user input.
// Initializes a new instance of the StringBuilder class
// and appends the given value if supplied
function StringBuilder()
{
var strings = [];
this.append = function (string)
{
string = verify(string);
if (string.length > 0) strings[strings.length] = string;
}
this.appendLine = function (string)
{
string = verify(string);
if (this.isEmpty())
{
if (string.length > 0) strings[strings.length] = string;
else return;
}
else strings[strings.length] = string.length > 0 ? "\r\n" + string : "\r\n";
}
this.clear = function () { strings = []; };
this.isEmpty = function () { return strings.length == 0; };
this.toString = function () { return strings.join(""); };
var verify = function (string)
{
if (!defined(string)) return "";
if (getType(string) != getType(new String())) return String(string);
return string;
}
var defined = function (el)
{
// Changed per Ryan O'Hara's comment:
return el != null && typeof(el) != "undefined";
}
var getType = function (instance)
{
if (!defined(instance.constructor)) throw Error("Unexpected object type");
var type = String(instance.constructor).match(/function\s+(\w+)/);
return defined(type) ? type[1] : "undefined";
}
}
Within the code of the second radio button (which will be the seventh and last function to complete), I tested the ScriptBuilder with data in a local variable, and it ran successfully and produced output into the output textarea. But I get no output from this next function that invokes a While loop:
function RegEx_Match_TheOnly_AllInstances()
{
inputString = document.getElementById("txtUserInput").value;
pattern = /(\s+the\s+)/ig; // Using an Flag (/i) to select either lowercase or uppercase version. Finds first occurrence either as a standalone word or inside a word.
//result = pattern.exec(inputString); // Finds the first index location
var arrResult; // Array for the results of the search.
var sb = getStringBuilder(); // Variable to hold iterations of the result and the text
while ((arrResult = pattern.exec(inputString)) !==null)
{
sb.appendLine = "Match: " + arrResult[0] ;
}
document.getElementById("txtRegExOutput").value = sb.toString();
/* Original code from C# version:
// string pattern = #"\s+(?i)the\s+"; // Same as above, but using Option construct for case insensitive search.
string pattern = #"(^|\s+)(?i)the(\W|\s+)";
MatchCollection matches = Regex.Matches(userTextInput, pattern);
StringBuilder outputString = new StringBuilder();
foreach (Match match in matches)
{
string outputRegExs = "Match: " + "\"" + match.Value + "\"" + " at index [" + match.Index + ","
+ (match.Index + match.Length) + "]" + "\n";
outputString.Append(outputRegExs);
}
txtRegExOutput.Text = outputString.ToString();
*/
} // End RegEx_Match_The_AllInstances
I left the commented code in to show what I had used in the C# code behind version to illustrate what I'm trying to accomplish.
The test input/string I used for this function is:
Don’t go there. If you want to be the Man, you have to beat The Man.
That should return two hits. Ideally, I want it to show the word that it found and the index where it found the word, but at this point I'd be happy to just get some output showing every instance it found, and then build on that with the index and possibly the lastIndex.
So, is my problem in my While loop, the way I'm applying the StringBuilder, or a combination of the two? I know the StringBuilder code works, at least when not being used in a loop and using some test data from the site I found that code. And the code for simply finding the first instance of "the" as a standalone or inside another word does work and returns output, but that doesn't use a loop.
I've looked through Stack Overflow and several other JavaScript websites for inspiration, but nothing I've tried so far has worked. I appreciate any help anyone can provide! (If you need me to post any other code, please advise and I'll be happy to oblige.)