Automatically add variables to array? - google-apps-script

In a google script I have written something to check my monthly expenses, which are listed in a google sheet.
Based on words the script finds, every line gets a category tag. It works fine, but the number of words to search for is getting big. And the array is getting big too.
I have listed 6 pairs (words to find, tag to add) - but in real version I have as many as 35. How can I create the pairs, and load everything automatically in the array?
This is my script:
function myFunction() {
// check usual suspects
var A1 = ["CAFE", "HORECA"]
var A2 = ["ALBERT", "AH"]
var A3 = ["VOMAR","Vomar"]
var A4 = ["HEMA","HEMA"]
var A5 = ["KRUID","Drogist"]
var A6 = ["RESTA", "Horeca"]
// in Array
var expenses = [A1,A2,A3,A4,A5,A6]
var ss = SpreadsheetApp.getActiveSheet();
var data = ss.getDataRange().getValues(); // read all data in the sheet
for (i in expenses)
{for(n=0;n<data.length;++n){ // iterate row by row and examine data in column A
if(data[n][3].toString().toUpperCase().match(expenses[i][0])==expenses[i][0]){ data[n][4] = expenses[i][1]};
// if column D contains 'xyz' then set value in index [5] (is column E)
}
Logger.log(data)
ss.getRange(1,1,data.length,data[0].length).setValues(data); // write back to the sheet
}
}

I can propose you that:
function multiPass(){
var searchCriterions = [
["CAFE","HORECA" ],
["ALBERT", "AH"],
["VOMAR","Vomar"],
["HEMA","HEMA"]
];
var dico = {};
var patt = "";
for (var i in searchCriterions) {
dico[searchCriterions[i][0]] = searchCriterions[i][1];
patt += "("+searchCriterions[i][0]+")";
if((Number(i)+1)<searchCriterions.length){
patt += "|";
}
}
var re = new RegExp(patt,"");
var ss = SpreadsheetApp.getActiveSheet();
var data = ss.getDataRange().getValues(); // read all data in the sheet
Logger.log(re);
for(n=0;n<data.length;++n){ // iterate row by row and examine data in column A
// THAT'S NOT COLUMN "A", 3 --> "D"
var test = data[n][3].toString().toUpperCase().match(re);
Logger.log(test);
if(test!==null){
data[n][4] = dico[test[0]]
};
}
ss.getRange(1,1,data.length,data[0].length).setValues(data); // write back to the sheet
}
instead of using variable for your "pairs" prefer to use a big table (it's less painfull to write)
then transform your pairs in object to quickly access the second argument of the pair and create a big regexp that check at once all the keywords instead of parsing them one by one.
Now as we are using a big array as search criterions we can totally imagine that this big array is loaded instead of hard coding it. If you have a sheet where the data is you can change the code this way:
var searchCriterions = SpreadsheetApp.getActive().getRange("namedRange").getValues();

Related

Vlookup + indexOf to find values in a CSV via Google App Script without using loop

The main idea is not to need looping to generate a VLOOKUP because it generates a huge slowdown when the amount of data is very large.
To VLOOKUP on data directly in the sheet I do as follows:
function myFunction() {
var s = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var searchValue = s.getRange("Test!A1").getValue();
var data = SpreadsheetApp.openById("XXXXXXXXXXXX").getSheetByName("Test 2");
var dataValues = data.getRange("A1:A").getValues();
var dataList = dataValues.join("ღ").split("ღ");
var index = dataList.indexOf(searchValue);
if (index === -1) {
s.getRange("Test!B1").setValue('off');
} else {
var row = index + 1;
var foundValue = data.getRange("D"+row).getValue();
s.getRange("Test!B1").setValue(foundValue);
}
}
But there is a big problem in this method, because when many different accounts try to access this sheet at the same time, the error type error: could not connect sheet xxxxx appears or causes huge delay sometimes.
So what was the solution I found? Publish spreadsheet pages as CSV so they can be used and this error doesn't happen when many accounts call the same spreadsheet.
Currently, as I haven't found a way to use indexOf using the first column when I import the CSV with several columns of data, I had to create a spreadsheet page only with the copy data of column A, and then I got to the final result of VLOOKUP like this:
(the value in var searchValue in this example case will be two)
function myFunction() {
var s = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var url_columnA = 'AAAAA';
var url_vlookup = 'BBBBB';
var dataSearch = Utilities.parseCsv(UrlFetchApp.fetch(url_columnA));
var dataList = dataSearch.join("ღ").split("ღ");
var searchValue = s.getRange("Test!A1").getValue();
var index = dataList.indexOf(searchValue);
if (index === -1) {
s.getRange("Test!B1").setValue('off');
} else {
var row = index;
var dataVlookup = Utilities.parseCsv(UrlFetchApp.fetch(url_vlookup));
var foundValue = dataVlookup[row][3];
s.getRange("Test!B1").setValue(foundValue);
}
}
Return example:
other number
var url_vlookup:
Col A
Col B
Col C
Col D
home
1
a
win
away
2
b
loose
one
3
c
number
two
4
d
other number
three
5
e
number again?
var url_columnA:
Col A
home
away
one
two
three
Is there any way to handle var url_vlookup data for search the value in column A so that it's not necessary to use this page var url_columnA separated or is the only way to do it without looping?
The first column can easily be separated after parsing using Array.map:
const dataVlookup = Utilities.parseCsv(UrlFetchApp.fetch(url_vlookup));
const url_columnA = dataVlookup.map(row => row[0])

How do I store a JSON loop value and send it to an empty sheet?

I'm pulling data from an API.
I parsed the JSON file and ran it through a loop to grab each index present within the file(14 indexes).
I believe my code stores the values in i?
So when I use log, it returns json[i][2], which logs 14 different values.(perfect)
But when I use return, it only returns 1 value into my google sheet.
What am I doing wrong?
Thanks!
function myKlines(){
var url='https://api1.binance.com/api/v3/klines?symbol=ADAUSDT&interval=1d&limit=14'
var source = UrlFetchApp.fetch(url).getContentText()
var json = JSON.parse(source)
for(var i=0; i<json.length; i++){
var loop = json[i][2];
Logger.log(loop);
}
By function =myKlines(A1)
function myKlines(code){
var url='https://api1.binance.com/api/v3/klines?symbol='+code+'&interval=1d&limit=14'
var source = UrlFetchApp.fetch(url).getContentText()
var json = JSON.parse(source)
for (var i=0;i<json.length;i++){json[i][0] = new Date(json[i][0])}
return json
}
for date/high/low/close
function myKlinesExtract(code){
var url='https://api1.binance.com/api/v3/klines?symbol='+code+'&interval=1d&limit=14'
var source = UrlFetchApp.fetch(url).getContentText()
var json = JSON.parse(source)
var result = []
result.push(['Date','High','Low','Close'])
for (var i=0;i<json.length;i++){
result.push([new Date(json[i][0]),json[i][2],json[i][3],json[i][4]])
}
return result
}
https://docs.google.com/spreadsheets/d/1JDT1TSwbAVcMrhWEHGH8jprt0u_gAnRqjY_W7W7SaQ4/copy
As you have it now, you are iterating through the variable json, but you aren't doing anything with the data.
To save the data from the loop, you need to put each iteration into an array or an object.
To make it easy, here it is with an array:
function myKlines(){
var url='https://api1.binance.com/api/v3/klines?symbol=ADAUSDT&interval=1d&limit=14'
var source = UrlFetchApp.fetch(url).getContentText()
var json = JSON.parse(source)
var output = []; //declaring an empty array
for (var i=0; i<json.length; i++){
var loop = json[i][2];
Logger.log(loop);
// add the contents of this iteration to the output array
output.push(loop);
}
Logger.log(output);
// send the output to a function to paste the data into your sheet.
// you could also just replace below with the paste code
pasteOutput(output);
}
Next, to paste it into a sheet, you need to build the range it will go into. I am guessing you want to paste the 14 indexes into different cells. Here is the 14 cells in a single column:
function pasteOutput(output){
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('your sheet name');
// get how many items there are in your array
var outputLength = output.length;
// this gets a range that starts at row 1, col 1, and has as many rows
//as your array has items, and is 1 column wide
var pasteRange = sheet.getRange(1,1,outputLength,1);
/*
Sheets takes values as a 2 dimensional array with
the inner array being a row, and the items inside the inner array the columns like this:
[[row1col1, row1col2, row1col3],[row2col1, row2col2, row2col3],[row3col1, row3col2, row3col3],...]
*/
// this statement will take each item in the output array,
//make it an array of one thing, and add that to the pasteArray
var pasteArray = output.forEach(item => pasteArray.push([item]));
pasteRange.setValues(pasteArray);
}
To put the cells in a single row, omit the pasteArray line, and change your pasteRange to (1,1,1 outputLength)
By this way, you will get all datas at once in sheet ADAUSDT
function myKlinesAll(){
var url='https://api1.binance.com/api/v3/klines?symbol=ADAUSDT&interval=1d&limit=14'
var source = UrlFetchApp.fetch(url).getContentText()
var json = JSON.parse(source)
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('ADAUSDT')
var headers = ["Open time","Open","High","Low","Close","Volume","Close time","Quote asset volume","Number of trades","Taker buy base asset volume","Taker buy quote asset volume","Ignore"]
for (var i=0;i<json.length;i++){json[i][0] = new Date(json[i][0])}
sheet.getRange(1,1,1,headers.length).setValues([headers])
sheet.getRange(2,1,json.length,json[0].length).setValues(json)
}
By this way, you will retrieve only column#2 (High)
function myKlines(){
var url='https://api1.binance.com/api/v3/klines?symbol=ADAUSDT&interval=1d&limit=14'
var source = UrlFetchApp.fetch(url).getContentText()
var json = JSON.parse(source)
var result = []
result.push(['Date','High'])
for (var i=0;i<json.length;i++){
result.push([new Date(json[i][0]),json[i][2]])
}
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('ADAUSDT')
sheet.getRange(1,18,result.length,result[0].length).setValues(result)
}

How to write to a cell when given a range with the onFormSubmit trigger? (Google apps script)

I am new to coding with google apps script and am trying to make a simple case checker for my spreadsheet.
In this case, I want to check if the first letter of a name is capitalized, and if it isn't, I want to replace it with a corrected version (same name, just with the first letter capitalized.)
This code returns an error when trying to run it in the editor (cannot read property "range" of undefined.) I presume this is because it is using an onFormSubmit trigger, so the only way to test it would be to actually submit a form response.
When a new form response IS posted and the function is triggered, it doesn't seem to do anything on the spreadsheet's end. The new cells that contain the first name and last name (or any of the new cells for that matter) simply don't change, first-letter-capitalized or not.
When using a separate function that refers to the cells the same way (taking a range of the newly submitted cells and designating them row[numberhere] within the for loop) it reads their strings just fine.
function caseCheck(r) {
var range = r.range;
var data1 = range.getValue();
for (var r in data1) {
var row1 = data1[r];
var firstName = row1[2].getValue;
var lastName = row1[3].getValue;
var firstNameC = firstName[0].toUpperCase();
var lastNameC = lastName[0].toUpperCase();
if (firstName[0] != firstNameC) {
var firstNameL = firstName.length();
var firstNameSS = firstName.substring(1,firstNameL);
var firstNameCorrected = firstNameC + firstNameSS;
row1[2].setValue(firstNameCorrected);
}
if (lastName[0] != lastNameC) {
var lastNameL = lastName.length();
var lastNameSS = lastName.substring(1,lastNameL);
var lastNameCorrected = lastNameC + lastNameSS;
row1[3].setValue(lastNameCorrected);
}
}
}
You have several issues with the code as shown.
I think you meant to use the plural range.getValues() not range.getValue() as .getValue just returns the Object inside the top left cell of the range. If data1 is a string, then for (var r in data1) is just looping over a string, not an array of values.
Assuming that you fix data1 so it is an array of arrays, then row1 is just a JavaScript array, presumably an array of strings, so the various places where you are using e.g. row1[2].getValue (which don't have parentheses anyways) and row1[2].setValue(firstNameCorrected) shouldn't have any effect because these are strings, not Range objects.
What you need to do is mutate the data array as needed, and then call range.setValues(data) with the mutated data.
function caseCheck(r) {
var range = r.range;
var data = range.getValues();
for (var r in data) {
var row = data[r];
// do whatever mutation to row you want
}
range.setValues(data);
}
As a side-note, personal preference/suggestion, this kind of imperative mutating code is very hard to read and maintain. Look into using more (and smaller) functions...
function caseCheck(r) {
var range = r.range;
var data = range.getValues();
var newData = data.map(rowCapitalizeFunction);
range.setValues(newData);
}
function rowCapitalizeFunction(row) {
var firstName = row[2];
// etc ... do what you need to do
return [/* send back a new array with new values, much easier to understand! */];
}

Converting Sheet data to a new Sheet

I'm trying to use Google Apps Script to communicate with Google Sheets to do the following:
We're trying to convert data from one Point of Sale system to a new one. In order to do this, I need to take certain columns of a sheet, manipulate them in various ways, and repopulate another sheet with the resulting data. I need to find products without a SKU number, and assign them a new one, starting at 10110 and incrementing from there.
function sku() {
// "From" Spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("New POS Data");
// "To" Spreadsheet
// Spreadsheet key: the string at the end of the URL
var ssraw = SpreadsheetApp.openById("1BH-j4cOW9Ntg6FlPNXmNCUId_pm9BgyAh0cwrwB4z_A");
var sheetraw = ssraw.getSheetByName("Old POS Data");
var max = sheet.getLastRow();
// SKU / UPC
var range = sheetraw.getRange(2,18,max);
var data = range.getValues();
// Assign new sku if no old one
var skunum=[10110];
var newData = new Array();
for (var y = 0; y <= max; y++) {
if (data[y]==""){
newData.push(skunum);
skunum++;
var skunum=[skunum];
}
else {newData.push(data[y]);}
}
sheet.getRange(2,3,max).setValues(newData);
}
This gives me the error "Incorrect range height, was 1 but should be 30 (line 26, file "SKU")"
If I remove the brackets around newData in the last line, I get "Cannot convert Array to Object[][]. (line 27, file "")"
This has been driving me mental, if anyone can offer help, I would be very grateful.
Even if you get a one row or one column vector with getData() you will get an Object[][].
when you do:
if (data[y]==""){
newData.push(skunum);
skunum++;
var skunum=[skunum];
}
You check if [cellFromColumn] == "" which will evaluate to false as it's an array.
You then push the new/old sku number to a simple array which, for spreadsheets, is one row of columns.
Instead you want to push into the array a one element array containin the sku number like this:
newData.push(skunum);
...
newData.push([data[y]]);
This will create an array of rows, each row is an array with one element (one column)

Importing data from pipedrive API in Google sheets. How to copy the data into the sheet in the correct columns and rows

I am trying to import all Pipedrive deals and writing the information I need in Google sheets.
What I am already able to do: Access the feed and parse the data to a variable.
And print this variable into 1 cell. Of course this isn't workable yet.
The code is somewhat scavenged (building while I'm learning)
// Standard functions to call the spreadsheet sheet and activesheet
function alldeals() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var sheet = ss.getActiveSheet();
//the way the url is build next step is to itterate between the end because api only allows a fixed number of calls (100) this way i can slowly fill the sheet.
var url = "https://api.pipedrive.com/v1/deals?start=";
var url2 = "&limit="
var start = 0;
var end = start+50;
var token = "&api_token=hiddenforobviousreasons"
//call the api and fill dataAll with the jsonparse.
//then the information is set into the
//dataSet so that i can refill datall with new data.
var response = UrlFetchApp.fetch(url+start+url2+end+token);
var dataAll = JSON.parse(response.getContentText());
var dataSet = dataAll;
//create array where the data should be put
var rows = [], data;
for (i = 0; i < dataSet.length; i++) {
data = dataSet[i];
rows.push([data.id, data.value,data.pipeline_id]); //your JSON entities here
}
dataRange = sheet.getRange(1, 1, rows.length, 3); // 3 Denotes total number of entites
dataRange.setValues(rows);
}
The error I am getting on the sheet.getRange.
What I want to achieve is put the data in the columns id, value, pipeline_id
Any pointers to what direction I need to go to solve this problem would be awesome! A fix would be nice to but some pointers would be more useful for my understanding.
The error i am getting is the following:
De coördinaten of afmetingen van het bereik zijn ongeldig. (regel 29, bestand 'dealpull5.0')
Loosely translated:
The coordinates of the size of the reach are invalid (line29,file "dealpull5.0")
Thanks for this post!
I reworked your code for some of my needs to return the array of matching deals directly.
I also changed the api request to only pull specific deal fields, pull back 500 deals, and use a PipeDriver filter we've setup.
// Standard functions to call the spreadsheet sheet and activesheet
function GetPipedriveDeals() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var sheet = ss.getActiveSheet();
//the way the url is build next step is to iterate between the end because api only allows a fixed number of calls (100) this way i can slowly fill the sheet.
var url = "https://api.pipedrive.com/v1/deals:(org_name,title,owner_name,status,pipeline_id)?start=";
var limit = "&limit=500";
var filter = "&filter_id=1";
var pipeline = 7; // put a pipeline id specific to your PipeDrive setup
var start = 0;
// var end = start+50;
var token = "&api_token=your-api-token-goes-here"
//call the api and fill dataAll with the jsonparse.
//then the information is set into the
//dataSet so that i can refill datall with new data.
var response = UrlFetchApp.fetch(url+start+limit+filter+token);
var dataAll = JSON.parse(response.getContentText());
var dataSet = dataAll;
//create array where the data should be put
var rows = [], data;
for (var i = 0; i < dataSet.data.length; i++) {
data = dataSet.data[i];
if(data.pipeline_id === pipeline){
rows.push([data.org_name, data.title, data.owner_name, data.status, data.pipeline_id]);//your JSON entities here
}
}
Logger.log( JSON.stringify(rows,null,2) ); // Log transformed data
return rows;
}
You've retrieved data from a web API, and transformed it into a 2-dimensional array (an array of rows, where each row is an array of cells). To confirm this, you can add a Logger call after the loop that grooms the data:
var rows = [], data;
for (i = 0; i < dataSet.length; i++) {
data = dataSet[i];
rows.push([data.id, data.value,data.pipeline_id]); //your JSON entities here
}
Logger.log( JSON.stringify(rows,null,2) ); // Log transformed data
If you run that, you should see something like this in your logs:
[--timestamp--] [
[ id1, value1, pipeline_id1 ],
[ id2, value2, pipeline_id2 ],
...
]
Next, you want to write it into a spreadsheet, which looks like this:
A B C
1 | ID | Value | Pipeline_Id |
+------+-------+-------------+
2 | | | |
3 | | | |
The error message is complaining that the size of the range and size of your data do not match.
Things to note:
Since there are column headers, the first cell that will contain data from the API call will be A2, which is also expressed as R2C1.
When using Range.setValues(), all rows of the provided data must be the same length as the width of the range. If necessary, you may need to pad or trim the data before calling Range.setValues().
It is convenient to match the range size to the size of the data; that way, you can easily adapt to changes in the data retrieved from the API.
var dataRange = sheet.getRange(2, 1, rows.length, rows[0].length);
dataRange.setValues(rows);