Google Apps Script For Loop Through Array Speed - arrays

I have a simple example of a function I am working on. I am trying to loop through a column of unique item IDs. If the item ID is found in the item ID column of another sheet, it pulls adjacent attributes from the data table, and assigns them in the same row. I have a function and it works, however, this is a base example. In reality I need to do this for 1000+ rows, and much larger data sets. It is currently taking 30-60 mins to run. I believe there is a much faster way to do this with arrays and using foreach and getvalues I'm just not sure how to get started. Any help would be greatly appreciated.
function example() {
var list = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("List");
var data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Data")
var listendRow = list.getLastRow();
var dataendRow = data.getLastRow();
var dataid = data.getDataRange().getValue();
for (var i = 2; i <= listendRow; i++) {
for (var j = 2; j <= dataendRow; j++){
var idnum = [list.getRange(i, 2,listendRow).getValue()];
var id = data.getRange(j, 3).getValue();
var name = data.getRange(j, 4).getValue();
var weight = data.getRange(j, 5).getValue();
if (idnum == id){
list.getRange(i, 3).setValue(name);
list.getRange(i, 4).setValue(weight);
}
}
}
}
Here is the link to the sheet:
https://docs.google.com/spreadsheets/d/1PPZKRXhiAAfFG1d-CU02MV_CSrqbdsCsyo_QADz5yiA/edit?usp=sharing

I believe your goal is as follows.
Your script works fine. Under this condition, you want to reduce the process cost of your script.
Modification points:
When I saw your sample Spreadsheet, V8 runtime is not used. Please enable V8 runtime. When V8 runtime is used, the process cost of the script can be reduced.
In your script, getValue and setValue are used in a loop. In this case, the process cost becomes high. Ref
SpreadsheetApp.getActiveSpreadsheet() can be declared one time.
In order to reduce the process cost of your script, how about the following modification?
Modified script:
Before you run this script, please enable V8 runtime.
function example2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var list = ss.getSheetByName("List");
var data = ss.getSheetByName("Data");
var obj = data.getRange("C2:E" + data.getLastRow()).getValues().reduce((o, [a, ...b]) => (o[a] = b, o), {});
var range = list.getRange("B2:B" + list.getLastRow());
var values = range.getValues().map(([b]) => obj[b] || [null, null]);
range.offset(0, 1, values.length, 2).setValues(values);
}
When this script is run, the values are retrieved from "Data" sheet and create an object for searching the ID. And, the values are retrieved from "List" sheet and an array for putting to the sheet is created. And also, the array is put to "List" sheet.
Note:
When you try to use this script without enabling V8 runtime, an error like Syntax error occurs. Please be careful about this.
This modified script is for your sample Spreadsheet. If your actual Spreadsheet is differnt structure from your provided sample one, this modified script might not be able to be used. Please be careful about this.
If you cannot use V8 runtime, please test the following modified script.
function example2b() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var list = ss.getSheetByName("List");
var data = ss.getSheetByName("Data");
var obj = data.getRange("C2:E" + data.getLastRow()).getValues()
.reduce(function (o, [a, b, c]) {
o[a] = [b, c];
return o
}, {});
var range = list.getRange("B2:B" + list.getLastRow());
var values = range.getValues().map(function ([b]) { return obj[b] || [null, null] });
range.offset(0, 1, values.length, 2).setValues(values);
}
References:
getValues()
setValues(values)
reduce()
map()

Related

Calling an API based on data within cells A2:A100 and outputting to cells G2:G100

I've been trying to figure out how to get a Google AppsScript to pull in an API for keyword rank tracking directly within Google Sheets.
The loop is required to dynamically pull in information from column A and output the keyword ranking position into column G.
The keywords are in cells A2-A100. The ranking position (which is the only thing we are pulling from the API) we are popping into the corresponding row in column G, starting from G2. For testing purposes, we've got the loop set from 1 to 3.
We're at a bit of a loss as to why this isn't working as expected, and would really appreciate a nudge in the right direction!
The issue is that the very first result always returns 'keyword = undefined' within the API, and returning a result of '-1', meaning that the first row is not read. We've tried updating the r to 0, to 2, and changing the r references to no avail.
This makes us think that there must be something wrong with the loop, rather than the rest of the code, but please do correct me if this is not the case.
The script we've gotten so far is;
function callAPI() {
//New loop
for (r = 1; r <= 3; r++) {
{
//Find keyword, encode query and url
var query = keyword;
var url =
'https://api.avesapi.com/search?apikey={{APIKEYREMOVEDFORPRIVACY}}&type=web&' +
'google_domain=google.co.uk&gl=gb&hl=en&device=mobile&output=json&num=100&tracked_domain={{CLIENTDOMAIN}}.com&position_only=true&uule2=London,%20United%20Kingdom' +
'&query=' +
encodeURIComponent(query);
//Call API and add to log
var response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
Logger.log(response);
//Get column value for keyword
var sheet = SpreadsheetApp.getActiveSheet();
var keyword = sheet.getRange(1 + r, 1).getValue();
}
//Set value of column
var results = sheet.getRange(1 + r, 7).setValue(response);
}
}
Additional edit:
So this is crystal clear, the desired input is;
keyword in A2 is read using the API and the output found (ranking position) is fed into G2.
the loop should then read A3, find the corresponding ranking position within the API, and adds that value to G3
rinse and repeat until the end of the loop.
Hopefully this is enough to go on, would really appreciate any advice, thank you!
Basically from TheMaster's comments you switch up your statements to this:
function callAPI() {
var sheet = SpreadsheetApp.getActiveSheet();
var keywords = sheet.getRange(2,1,3).getValues();
var responses = [];
//New loop
for (r = 0; r <= 2; r++) {
//Find keyword, encode query and url
var query = keywords[r][0];
var url =
'https://api.avesapi.com/search?apikey={{APIKEYREMOVEDFORPRIVACY}}&type=web&' +
'google_domain=google.co.uk&gl=gb&hl=en&device=mobile&output=json&num=100&tracked_domain={{CLIENTDOMAIN}}.com&position_only=true&uule2=London,%20United%20Kingdom' +
'&query=' +
encodeURIComponent(query);
//Call API and add to log
var resp = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
Logger.log(resp);
responses.push([resp]);
}
//Set value of column
sheet.getRange(2,7,3).setValues(responses);
}
Note that I moved the sheet declaration outside the loop, it needs to be only called once.
EDIT: I updated the code to follow best practices in the tag info page. Note the usage of arrays as return values of getValues() and parameter of setValues().

two active sheets in google sheets update error based on cell comparison

I'm trying to get this code working but I know I'm missing something. what the code should do is: to find a specific text from another cell using another sheet reference, so there are two sheets in the google sheet below:'BSR DATA' and 'ENTRY FORM'. from ENTRY FORM,cell g6 will compare its data to the first column of BSR DATA and if it meets the criteria, it will update the specific row of the array/cell reference with "new value".
link to google sheets:https://docs.google.com/spreadsheets/d/1kBuczydffPFBEfy2oegC1WsKXMjSxbGNRzWLCKurOMs/edit?usp=drivesdk
function UpdateBSRSpecial()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var testForm = ss.getSheetByName("ENTRY FORM");
var testTable = ss.getSheetByName("BSR DATA");
var testFormValue = testForm.getRange("G6").getValue();
var rangeData = testTable.getDataRange();
var lastColumn = rangeData.getLastColumn();
var lastRow = rangeData.getLastRow();
for(var i=0;i>lastRow;i++){
var dataID = testForm.getRange(i,1).getValue();
if(testFormValue == dataID)
{
testTable.getRange(i,6).setValue("new value");
};
};
};
The existing script is updating all rows because it loops with for(var i=0;i>lastRow;i++){. The following script demonstrates the process to search for a discrete term.
I am not going to deal with the updating of the "new value" since that is not clear from the question.
Aspects to note
the script gets all the data on BSR - var bsrdata = bsr.getRange(1,1,bsrLR,bstLC).getValues();. This can be used later when updating values.
using the Javascript map method, Column A is extracted as a separate array for the search - var bsrColA = bsrdata.map(function(e){return e[0];});//[[e],[e],[e]]=>[e,e,e]
the search is conducted using the Javascript indexOf method. Note: if a match is found, the method returns the index number of the search term within the array; if no match is found, the method returns "-1".
the script tests to see if the search was successful: if (result !=-1){
indexOf returns a zero-based index, therefore the actual row number is the index plus one: var row = result+1;
function so5866895401() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formsheetname = "ENTRY FORM";
var form = ss.getSheetByName(formsheetname);
var bsrname = "BSR DATA";
var bsr = ss.getSheetByName(bsrname);
// get input cell on form
var searchterm = form.getRange("G6").getValue();
// Logger.log(searchterm);// DEBUG
//get data from BSR
var bsrLR = bsr.getLastRow();
var bstLC = bsr.getLastColumn();
var bsrdata = bsr.getRange(1,1,bsrLR,bstLC).getValues();
// get column A of BSR
var bsrColA = bsrdata.map(function(e){return e[0];});//[[e],[e],[e]]=>[e,e,e]
// Logger.log(bsrColA);// DEBUG
// search for the searchterm in ColumnA of BSR
var result = bsrColA.indexOf(searchterm); // zero-based
if (result !=-1){
var row = result+1;
Logger.log("result = "+result+", row = "+row);
}
}

In a form, I need to populate the contents of same spreadsheet column like 50 times. Is there a way to script this logic with fewer iterations

I'm working on a form where I need to pull the contents of a spreadsheet column like 50 times, to try to input multiple items from a list. I see that I can do this by defining a few variables and redoing a small piece of Script again and again. I want to see if anyone can help me overcome this lengthy script to make it smaller with fewer iterations. Thanks.
function updateForm(){
// call the form and connect to the drop-down items
var Form_SQ = FormApp.openById("FORM ID");
var SQ_IT01_List = Form_SQ.getItemById("ITEM 01").asListItem();
var SQ_IT02_List = Form_SQ.getItemById("ITEM 02").asListItem();
//Similarly defining upto 50 dropdown lists.
var SS01 = SpreadsheetApp.getActive();
var SQ_IT01_Names = SS01.getSheetByName("Sheet2");
var SQ_IT02_Names = SS01.getSheetByName("Sheet2");
//Similarly defining upto 50 names lists.
// Item_01 Part Number Dropdown
var SQ_IT01_Values = SQ_IT01_Names.getRange(2, 1, SQ_IT01_Names.getMaxRows() - 1).getValues();
var SQ_IT01_Items = [];
for(var i = 0; i < SQ_IT01_Values.length; i++)
if(SQ_IT01_Values[i][0] != "")
SQ_IT01_Items[i] = SQ_IT01_Values[i][0];
SQ_IT01_List.setChoiceValues(SQ_IT01_Items);
// Item_02 Part Number Dropdown
var SQ_IT02_Values = SQ_IT01_Names.getRange(2, 1, SQ_IT02_Names.getMaxRows() - 1).getValues();
var SQ_IT02_Items = [];
for(var i = 0; i < SQ_IT02_Values.length; i++)
if(SQ_IT02_Values[i][0] != "")
SQ_IT02_Items[i] = SQ_IT02_Values[i][0];
SQ_IT02_List.setChoiceValues(SQ_IT02_Items);
//Similarly defining upto 50 lookup lists.
}
Problem
Reusing code and making use of loops. Scripting is all about efficiency (see DRY principle): make as little assignments and same-functionality coding as possible - use loops, move reusable code snippets to functions that can be called on demand, etc.
Solution
This sample makes several assumptions:
SQ_IT01_Names is different for each item (in your sample it always is Sheet2 - if this is the case, you don't have to reassign it 50 times, one variable assignment will do just fine).
You intended to do something when a value is an empty string (the sample just filters them out). As you use the [index] notation, those values in the resulting Array will be undefined (and that's not something one would want in an Array of choice values).
All items are choice items (if you need id filtering, the sample is easily expanded).
function updateForm() {
var form = FormApp.openById("FORM ID");
//access every item;
var items = form.getItems();
var ss = SpreadsheetApp.getActive();
//loop over items;
items.forEach(function(item,i){
var namesSheet = ss.getSheetByName('Sheet'+i); //assuming this is diff each time;
var namesRange = namesSheet.getRange(2,1,namesSheet.getLastRow());
var namesValues = namesRange.getValues();
//map values to first column;
namesValues = namesValues.map(function(value){
return value[0];
});
//filter out undefined (undefined and false functional equivalence);
namesValues = namesValues.filter(function(value){
return value;
});
item.asListItem().setChoiceValues(namesValues);
});
}
Notes
Please, use closures {} with loops and if statements, this way you'll be able to keep track of which statements are enclosed in it and save yourself debugging time when looping / conditioning multiple statements.
Since you only need rows that have data in them, use the getLastRow() method instead of the getMaxRows()-1 calc you have to perform in your script.
Reference
forEach() method reference;
filter() method reference;
map() method reference;
getLastRow() method reference;

setFormulas and rangeList

I'm trying to setFormulas over a range of non-contiguous cells. I need a formula (they're all different) set every 30 cells in a single column (c).
It works to setFormula for each cell, but creating 56 variables seems unnecessary. I can get the formulas but not set them as intended. I also tried using getRangeList but I'm not sure that does what I think it does. Any advice?
function test() {
var spreadsheetU09U10 = SpreadsheetApp.openById('some url');
var sheetU09 = spreadsheetU09U10.getSheetByName('TEST');
var sheetU10 = spreadsheetU09U10.getSheetByName('U10');
var sheetDATA = spreadsheetU09U10.getSheetByName('Sheet4');
//U09 SHEET
//var rangeListU09 = sheetU09.getRangeList(['C4','C34','C64','C94','C124','C154','C184','C204','C234','C264','C294','C324','C354','C384','C404','C434','C464','C494',
//'C524','C554','C584','C604','C634','C664','C694','C724','C754','C784']);
//Logger.log(rangeListU09);
var startRow = 4;
var startColumn = 3;
var numRows = sheetU09.getLastRow();
var numColumns = 1;
var range = sheetU09.getRange(startRow, startColumn, numRows, numColumns);
var getFormulasU09 = sheetDATA.getRange('C30:C57').getFormulas();
//Logger.log(getFormulasU09);
Logger.log(getFormulasU09.length);
for (var i = 0; i < getFormulasU09.length; i++) {
var setFormulasU09 = range.setFormulas(getFormulasU09);
Logger.log(setFormulasU09);
startRow = startRow + 29;
}
It isn't clear exactly where the formulas you are using are originating from, but the RangeList class can help reduce the read time, even if you use it just to call getRanges. If the formula is the same in R1C1 format, then you can very effectively use RangeList#setFormulaR1C1.
Assuming you have formulas in one region that must be written verbatim in a disjoint set of cells:
const wb = SpreadsheetApp.getActive();
// Assuming only text formulas, not actual "entered" formulas
const formulas = wb.getSheetByName("formulas").getDataRange()
.getValues()
.map(function (row) { return row[0]; });
const sheet = wb.getSheetByName("some name");
const destinations = [
// Depending on the relationship between destinations, one could programmatically generate these
];
// Efficiently acquire references to multiple disjoint Ranges
const rl = sheet.getRangeList(destinations);
// Assume the i-th formula goes in the i-th range
rl.getRanges().forEach(function (rg, i) {
rg.setFormula(formulas[i]);
});
// The RangeList makes uniformly formatting these disjoint ranges extremely simple
rl.setFontWeight('bold');
...
Reference
- RangeList
You want to put formulas to the individual cells.
You want to put 28 formulas to cells of ['C4','C34','C64','C94','C124','C154','C184','C204','C234','C264','C294','C324','C354','C384','C404','C434','C464','C494', 'C524','C554','C584','C604','C634','C664','C694','C724','C754','C784'] in the sheet of TEST.
If my understanding is correct, how about using values.batchUpdate of Sheets API? The flow of this script is as follows.
Set range list as 1 dimensional array.
Retrieve formulas.
Create request body for sheets.spreadsheets.values.batchUpdate.
In order to use this script, please enable Sheets API at Advanced Google Services and API console. You can see about how to enable Sheets API at here.
Sample script:
function test() {
var spreadsheetId = "### spreadsheetId ###"; // Please set this.
var sheetName = "TEST";
var spreadsheetU09U10 = SpreadsheetApp.openById(spreadsheetId);
var sheetU09 = spreadsheetU09U10.getSheetByName(sheetName);
// var sheetU10 = spreadsheetU09U10.getSheetByName('U10'); // This is not used in this script.
var sheetDATA = spreadsheetU09U10.getSheetByName('Sheet4');
var rangeListU09 = ['C4','C34','C64','C94','C124','C154','C184','C204','C234','C264','C294','C324','C354','C384','C404','C434','C464','C494', 'C524','C554','C584','C604','C634','C664','C694','C724','C754','C784'];
var getFormulasU09 = sheetDATA.getRange('C30:C57').getFormulas();
rangeListU09 = rangeListU09.map(function(e) {return sheetName + "!" + e});
var resource = {
data: rangeListU09.map(function(e, i) {return {range: e, values: [[getFormulasU09[i][0]]]}}),
valueInputOption: "USER_ENTERED",
};
Sheets.Spreadsheets.Values.batchUpdate(resource, spreadsheetId);
}
Note:
From your question, I'm not sure about the detail formulas. If the a1Notation of each formulas is required to be modified, can you provide a sample spreadsheet including the formulas?
Reference:
sheets.spreadsheets.values.batchUpdate
If I misunderstand your question, please tell me. I would like to modify it.
I'm assuming that you want to copy the whole column starting from the cell locations in the array. That wasn't really clear to me.
function test109() {
var ss=SpreadsheetApp.getActive();
var shU09=ss.getSheetByName('35');//formulas get copied into here starting at row 4
var shDATA=ss.getSheetByName('36');//formulas stored in here C30:C57
var fA=shDATA.getRange('C30:C57').getFormulas();
var dA=['C4','C34','C64','C94','C124','C154','C184','C204','C234','C264','C294','C324','C354','C384','C404','C434','C464','C494','C524','C554','C584','C604','C634','C664','C694','C724','C754','C784'];
for(var i=0;i<dA.length;i++){
var rgs=Utilities.formatString('%s:%s',dA[i],shU09.getRange(dA[i]).offset(fA.length-1,0).getA1Notation());//this uses range.offset to calculate the correct range in A1Notation.
shU09.getRange(rgs).setFormulas(fA);
}
}
As it turns out I just noticed that there are 28 locations and 28 formulas. Perhaps that was intentional and you want to copy a different formula in each location then this version would do that.
function test109() {
var ss=SpreadsheetApp.getActive();
var shU09=ss.getSheetByName('35');//formulas get copied into here starting at row 4
var shDATA=ss.getSheetByName('36');//formulas stored in here C30:C57
var fA=shDATA.getRange('C30:C57').getFormulas();
var dA=['C4','C34','C64','C94','C124','C154','C184','C204','C234','C264','C294','C324','C354','C384','C404','C434','C464','C494','C524','C554','C584','C604','C634','C664','C694','C724','C754','C784'];
for(var i=0;i<dA.length;i++){
shU09.getRange(dA[i]).setFormula(fA[i][0]);
}
}
Range Offset

Google Apps Script Replace and update cell within a range

I have a Google spreadsheet that I'm trying to remove the word "woo" within a range of cells
So far I've managed to loop through the results and log the results, however I haven't figured how to update that information in the spreadsheet itself.
Any guidance would be welcomed
Thank you
function myFunction () {
var ss = SpreadsheetApp.getActiveSheet().getRange('B:B')
var data = ss.getValues();
for (var i = 0; i < data.length; i++) {
var text = data[i].toString();
var finaltext = text.replace(/woo/g, "");
data[i] = finaltext;
Logger.log(data[i]);
}
}
Use setValues()
Notes:
Usually ss is used as a shorthand for spreadsheet, as it's used on the code for a range it's better to use range as a variable name.
setValues() returns a 2D array, so data[i] returns an array of row values rather than a cell value. To get/set cell values, use data[i][0] notation.
Considering the above replace
var ss = SpreadsheetApp.getActiveSheet().getRange('B:B')
by
var range = SpreadsheetApp.getActiveSheet().getRange('B:B')
then add the following line after the for block.
range.setValues(data);
Regarding text var declaration, replace
var text = data[i].toString();
to
var text = data[i][0].toString();
Using open ended references like B:B could lead to problems. To avoid them be sure to keep the sheet rows at minimum or better instead of using an open ended reference use something like B1:B10.

Resources