Following the bog post https://community.snowflake.com/s/article/How-to-USE-SHOW-COMMANDS-in-Stored-Procedures
The result I get it NaN, which makes sense since the return value is set to float in this Blog Post.
I have tried setting the return value to varchar, string etc. but Iget different results like Object object etc.
CREATE OR REPLACE PROCEDURE SHOP(CMD VARCHAR)
returns float not null
LANGUAGE JAVASCRIPT
EXECUTE AS CALLER
AS $$
var stmt = snowflake.createStatement(
{
sqlText:${CMD}
}
);
var res = stmt.execute();
return res
$$
;
The expected return is a list of tables, actual results are NaN.
The stmt.execute() call returns a ResultSet object that will become NaN when converted to FLOAT. You need to use the ResultSet object to extract the data returned from the query.
Also remember variable references in JavaScript are plain, ie. CMD.
The PROCEDURE return type FLOAT will not work if you want "The expected return is a list of tables", whatever that is. I suspect you want ARRAY or the totally flexible VARIANT. Both work with the following:
$$
var stmt = snowflake.createStatement( { sqlText: CMD } );
var rs = stmt.execute();
var result = [];
while (rs.next())
result.push(rs.getColumnValue(1));
return result;
$$;
Please look at the Stored Procedures API documentation for details.
If you have multiple columns, the code becomes slightly more complicated:
var result = [], row, col;
while (rs.next()) {
row = [];
for (col = 1; col <= stmt.columnCount; col++)
row.push(rs.getColumnValue(col));
result.push(row);
}
Hardcore JavaScript programmers might compress this to:
var result = [], cols = Array.from({ length: stmt.columnCount }, (v, i) => i + 1);
while (rs.next()) result.push(cols.map(c => rs.getColumnValue(c)));
A final variant where the first result row contains the column names and the following rows contain the data from the result set, accessible as result[row][column]:
var result =
[ Array.from({ length: stmt.columnCount }, (v, i) => stmt.getColumnName(i + 1)) ];
while (rs.next()) result.push(result[0].map(cn => rs.getColumnValue(cn)));
Related
I'm trying to loop through the top title row of my spreadsheet to find the index number of a column based on the title name so that if someone inserts a column my code won't break. Here's what I have so far:
var sheet = SpreadsheetApp.getActive().getSheetByName('RawData');
var values = sheet.getRange(1,1,sheet.getMaxRows(),sheet.getMaxColumns()).getValue(); //line that's breaking
range.setNote("inside function");
var i = 1;
while(values[1][i] != name) {
i++;
}return i;
}
The code appears to break on the line where I set 'values,' and I can't figure out how to access the array I created in order to check which one contains the same string as the 'name' parameter. The setNote line is just for testing purposes, you can ignore it.
Thanks in advance.
EDIT
function getColumnByName() {
var name = "Ready For Testing?";
var ss=SpreadsheetApp.getActive().getSheetByName('RawData');
var vA=ss.getRange(1,1,ss.getLastRow(),ss.getLastColumn()).getValues();
var hA=vA.shift();//returns header array vA still contains the data
var idx={};
var index = hA.forEach(function(name,i){idx[name]=i});//idx['header name'] returns index
return index;
}
I set name just for testing purposes, it will be passed as a parameter when I use the actual function
Try
function getColumnByName() {
var name = "Ready For Testing?";
var ss=SpreadsheetApp.getActive().getSheetByName('RawData');
var vA=ss.getDataRange().getValues();
var hA=vA.shift();//returns header array vA still contains the data
var idx= hA.indexOf(name);
if(idx === -1 ) throw new Error('Name ' + name + ' was not found');
return idx + 1; // Returns the name position
}
Instead of
var values = sheet.getRange(1,1,sheet.getMaxRows(),sheet.getMaxColumns()).getValue();
and instead of
var vA=ss.getRange(1,1,ss.getLastRow(),ss.getLastColumn()).getValues();
use
var values = sheet.getDataRange().getValues();
getValue() only returns the value of top-left cell
using sheet.getMaxRows() / sheet.getMaxColumns() returns the sheet last row / column which could cause getting a lot of blank rows / columns.
compared with the second line code (getLastRow() / getLastColumn()) the proposed code line is "cheaper" (one call to the Spreadsheet Service instead of three (1. getRange, 2. getLastRow, 3 getLastColumn Vs. getDataRange) to get the data range and usually is faster too.
Regarding
var index = hA.forEach(function(name,i){idx[name]=i});
forEach returns undefined ref. Array.prototype.forEach
Try this:
function myFunction() {
var ss=SpreadsheetApp.getActive()
var sh=ss.getSheetByName('RawData');
var vA=sh.getRange(1,1,sh.getLastRow(),sh.getLastColumn()).getValues();
var hA=vA.shift();//returns header array vA still contains the data
var idx={};
hA.forEach(function(h,i){idx[h]=i});//idx['header name'] returns index
var end="is near";//set a break point here and run it down to here in the debugger and you can see hA and idx
}
hA is the header name array
idx is an object that returns the index for a given header name
idx is all you need just learn to put these three lines of code in your script in the future and you won't need to use an external function call to get the index.
var hA=vA.shift();//returns header array vA still contains the data
var idx={};
hA.forEach(function(h,i){idx[h]=i});//idx['header name'] returns index
vA still contains all of your data
I'm facing an error when inserting a JSON object into a VARIANT data type column, from within a stored procedure in Snowflake.
Below is the reference code:
create or replace procedure test1234()
returns varchar not null
language javascript
as
$$
var birthDateVar = 'BIRTH_DATE';
var genderVar = 'GENDER';
var countryVar = 'COUNTRY';
var loanVar = 'LOAN_AMOUNT';
var emailVar = 'EMAIL';
var tableName = 'SF_STRUCT_STAGE_RAW';
var UPDATE_DATE = "2019-01-01";
var email = "XXX#gmail.com";
var row_num = "1";
var person_id = "1";
var EMAILERROR = {
"row_num": row_num,
"person_id": person_id,
"tableName": tableName,
"fieldName": emailVar,
"fieldValue": email,
"errorDesc": 'value is invalid email'
};
var cmd = "insert into error_details_log values(:1,:2);";
var stmt = snowflake.createStatement(
{
sqlText: cmd,
binds: [EMAILERROR, UPDATE_DATE]
}
);
stmt.execute();
return emailError;
$$;
Inserting Object types via binding variables directly is not currently supported from within Stored Procedures. Quoting the relevant portion from the Snowflake documentation:
When you bind JavaScript variables to SQL statements, Snowflake converts from the JavaScript data types to the SQL data types. You can bind variables of the following JavaScript data types:
number
string
SfDate
However, the example in the documentation for TIMESTAMP datatypes involving use of strings and recasting can be adapted for your use-case, using the PARSE_JSON(…) function and JSON.stringify(…) JavaScript API:
[…]
// Convert Object -> String
var EMAILERROR_str = JSON.stringify(EMAILERROR);
// Ask Snowflake to parse String as JSON (String -> Object)
var cmd = "insert into error_details_log select PARSE_JSON(:1), :2;";
var stmt = snowflake.createStatement(
{
sqlText: cmd,
// Bind the String value, not the Object one
binds: [EMAILERROR_str, UPDATE_DATE]
}
);
[…]
What I'm trying to do it have a filter object that is populated like so
var filter = new Filter
{
ThingID = 1,
Keywords = new[] { "op", "s" }
};
And then be able to build up the query like this:
var sb = new StringBuilder();
sb.AppendLine("select * from Stuff where 1=1");
if (filter.ThingID.HasValue)
{
sb.AppendLine(" and ThingID = #ThingID");
}
if (filter.Keywords != null)
{
for (int i = 0; i < filter.Keywords.Length; i++)
{
string keyword = filter.Keywords[i];
if (!string.IsNullOrWhiteSpace(keyword))
{
sb.AppendLine(" and ( Model like '%' || #Keywords" + i + " || '%' )");
}
}
}
var sql = sb.ToString();
var results = Query<Stuff>(sql, filter).ToList();
This works fine if just the ThingID prop is populated, but as far as I can tell Dapper is not feeding the Keywords in as a parameter in any way. Is this possible with Dapper, or does it only work in the context of " where Keywords in #Keywords"?
Parameters need to match by name; you are adding parameters like #Keywords17, but there is no Keywords17 property for it to add. It doesn't interpret that as Keywords[17], if that is what you mean. There is some automatic expansion of flat arrays, but that is intended for expanding in #Keywords (although the expansion itself is not specific to in). There is not currently something that would help you automatically there; I would suggest DynamicPaameters instead:
var args = new DynamicParameters();
args.Add("ThingID", 1);
...
if (!string.IsNullOrWhiteSpace(keyword))
{
sb.AppendLine(" and ( Model like '%' || #Keywords" + i + " || '%' )");
args.Add("Keywords" + i, keyword);
}
...
var cmd = new CommandDefinition(sql, args, flags: CommandFlags.NoCache);
var results = Query<Stuff>(cmd).AsList();
note the subtle two changes at the end here - CommandFlags.NoCache will help avoid building lots of lookup entries for similar but different SQL (although you might choose to pay this to reduce per-item cost, up to you). The AsList instead of ToList avoids an extra list allocation.
I am attempting to build a dynamic Sql query for multiple search terms. I understand in general how to use the builder, but am not sure what to do in the loop since I actually need the #term to be different each time (I think). Not just in the query, but in the anonymous type as well to match.
I could use a string.Format in the query string, but not sure how to match it in the anonymous type?
public async Task<List<Thing>> Search(params string[] searchTerms)
{
var builder = new SqlBuilder();
var template = builder.AddTemplate("SELECT * /**select**/ from ThingTags /**where**/ ");
for (int i = 0; i < searchTerms.Length; i++)
{
builder.OrWhere("value LIKE #term", new { term = "%" + searchTerms[i] + "%" });
}
...
}
in the current form the query that gets created for terms "abc" "def" "ghi" is
CommandType: Text, CommandText: SELECT * from ThingTags WHERE ( value LIKE #term OR value LIKE #term OR value LIKE #term )
Parameters:
Name: term, Value: %ghi%
Well here is one way to do the query building. I didn't realize that the parameters could be a Dictionary initially.
public async Task<List<Thing>> Search(params string[] searchTerms)
{
var builder = new SqlBuilder();
var template = builder.AddTemplate("SELECT * /**select**/ from ThingTags /**where**/ ");
for (int i = 0; i < searchTerms.Length; i++)
{
var args = new Dictionary<string, object>();
var termId = string.Format("term{0}", i.ToString());
args.Add(termId, "%" + searchTerms[i] + "%");
builder.OrWhere("value LIKE #" + termId, args);
}
...
}
You can easily create that dynamic condition using DapperQueryBuilder:
var query = cn.QueryBuilder($#"
SELECT *
FROM ThingTags
/**where**/");
// by default multiple filters are combined with AND
query.FiltersType = Filters.FiltersType.OR;
foreach (var searchTerm in searchTerms)
query.Where($"value like {searchTerm}");
var results = query.Query<YourPOCO>();
The output is fully parametrized SQL (WHERE value like #p0 OR value like #p1 OR...). You don't have to manually manage the dictionary of parameters.
Disclaimer: I'm one of the authors of this library
I was trying to use the Where and OrWhere methods of SqlBuilder for Dapper, but it is not acting like how I would expect.
The edited portion of this question is basically what I ran into. Since it didn't receive a response, I'll ask it here.
var builder = new SqlBuilder();
var sql = builder.AddTemplate("select * from table /**where**/ ");
builder.Where("a = #a", new { a = 1 })
.OrWhere("b = #b", new { b = 2 });
I expected select * from table WHERE a = #a OR b = #b
but I got select * from table WHERE a = #a AND b = #b
Is there any way to add an OR to the where clause using the SqlBuilder?
I think it's just a matter of changing the following in the SqlBuilder class to say OR instead of AND, but I wanted to confirm.
public SqlBuilder OrWhere(string sql, dynamic parameters = null)
{
AddClause("where", sql, parameters, " AND ", prefix: "WHERE ", postfix: "\n", IsInclusive: true);
return this;
}
Nevermind. I looked through the SqlBuilder code and found that if there is a mixture of Where and OrWhere, it will do the following:
Join all the AND clauses
Join all the OR clauses separately
Attach the OR clauses at the end of the AND clauses with an AND
If you don't have more than 1 OrWhere, then you won't see any OR.
I'll modify my query logic to take this into account
You have to change your query into:
var builder = new SqlBuilder();
var sql = builder.AddTemplate("select * from table /**where**/ ");
builder.OrWhere("a = #a", new { a = 1 })
.OrWhere("b = #b", new { b = 2 });
In case you want to try another alternative, DapperQueryBuilder may be easier to understand:
var query = cn.QueryBuilder($#"
SELECT *
FROM table
/**where**/
");
// by default multiple filters are combined with AND
query.FiltersType = Filters.FiltersType.OR;
int a = 1;
int b = 2;
query.Where($"a = {a}");
query.Where($"b = {b}");
var results = query.Query<YourPOCO>();
The output is fully parametrized SQL (WHERE a = #p0 OR b = #p1).
You don't have to manually manage the dictionary of parameters.
Disclaimer: I'm one of the authors of this library