Code to reduce execution time by array / caching??? Google App Script - arrays

I'm using the following code to compile a list of hastags which have been seperated from a list of twitter tweets.
This code ignores all rows where the hashtag cell has no value or an error value.
function CopyProcessedHashtags() {
var sSheet = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = sSheet.getSheetByName("SortedIntoRows");
var targetSheet = sSheet.getSheetByName("HashtagsList");
var lastRow = sourceSheet.getLastRow();
for (var i = 3; i <= lastRow; i++) {
var cell = sourceSheet.getRange("E" + i);
var val = cell.getValue();
if (val != "") {
if (val != "#VALUE!") {
if (val != "#REF!") {
var sourceRange = sourceSheet.getRange("B" + i + ":" + "E" + i);
var targetRow = targetSheet.getLastRow();
targetSheet.insertRowAfter(targetRow+1);
var targetRange = targetSheet.getRange("B" + (targetRow+1));
sourceRange.copyTo(targetRange, {contentsOnly: true});
}}}}}
};
The trouble is that there is sometimes thousands of rows and execution time is well over 6 mins.
I know batching can reduce execution time...
EXAMPLE
for (var i = 1; i <= 100; i++) {
SpreadsheetApp.getActiveSheet().deleteRow(i);
to
SpreadsheetApp.getActiveSheet().deleteRows(i, 100);
... I just don't know how to rewrite my code to work in this manner.
Still very new to google app script.
If somebody could give me a nudge in the right direction you'r help would be VERY much appreciated.
Thanks!!

You can use getValues() to retrieve all data within the range and filter it using Array.filter().
Example:
Code:
function CopyProcessedHashtags() {
var sSheet = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = sSheet.getSheetByName("SortedIntoRows");
var targetSheet = sSheet.getSheetByName("HashtagsList");
var data = sourceSheet.getDataRange().getValues();
var filteredData = data.filter(dataRow => (dataRow[4] != "#VALUE!" && dataRow[4] != "#REF!" && dataRow[4] != ""));
targetSheet.getRange(1,1, filteredData.length, filteredData[0].length).setValues(filteredData);
}
Sample Data:
Output:
References:
getValues()
setValues()
Array.filter()

Related

Why is my Array returning a partial value?

I am trying to use textFinder to return the data from recordsSheet; I expect cslData[0] to return the employee ID number; however, it is returning the character in the 0 position of the string value.
function textFinder(findID){
var recordData;
var findRecord = recordsSheet.createTextFinder(findID);
var foundRange = findRecord.findNext();
var fRng = recordsSheet.getRange(foundRange.getRow(),1,1,9)
while(null != foundRange){
recordData.push(fRng.getValues());
foundRange = findRecord.findNext();
}
return(recordData);
}
var ss = SpreadsheetApp.getActiveSpreadsheet();
var recordsSheet = ss.getSheetByName("Active Records");
function onFormSubmit(e) {
var eRng = e.range;
var eRow = eRng.getRow();
var colA = ss.getRange('A' + eRow).getValue()
//The second value of the form response (e) is in Column A
Logger.log("Call txt finder")
var cslData = textFinder(colA).toString().split(",").join();
Logger.log("cslData: " + cslData)
Logger.log("cslData[0]: " + cslData[0])
Logger.log("cslData[1]: " + cslData[1])
Logger.log("cslData[2]: " + cslData[2])
Logger.log("cslData[0][0]: " + cslData[0][0])
}
I was expecting cslData[0] to return "100###5"
Modification points:
In your script, eRow is not declared. And, e is not used. Please be careful about this. In this modification, it supposes that eRow is declared elsewhere.
In your script, var recordData; is not an array. So, I think that an error occurs at recordData.push(fRng.getValues());. So, I'm worried that your showing script might be different from your tested script.
If var recordData; is var recordData; = [], about your current issue, in your script, the array of recordData is converted to the string by var cslData = textFinder(colA).toString().split(",").join();. By this, cslData[0] returns the top character. I thought that this is the reason for your issue.
And, if var recordData; is var recordData; = [], recordData is 3 dimensional array.
In your situation, I thought that findAll might be useful.
When these points are reflected in your script, how about the following modification?
Modified script:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var recordsSheet = ss.getSheetByName("Active Records");
function textFinder(findID) {
return recordsSheet
.createTextFinder(findID)
.findAll()
.map(r => recordsSheet.getRange(r.getRow(), 1, 1, 9).getValues()[0]);
}
function onFormSubmit(e) {
var colA = ss.getRange('A' + eRow).getValue();
var cslData = textFinder(colA);
Logger.log("cslData: " + cslData);
if (cslData.length > 0) Logger.log("cslData[0]: " + cslData[0][0]);
}
or, I think that you can also the following script.
function onFormSubmit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var recordsSheet = ss.getSheetByName("Active Records");
var eRow = ###; // Please set this.
var findID = ss.getRange('A' + eRow).getValue();
var cslData = recordsSheet.createTextFinder(findID).findAll().map(r => recordsSheet.getRange(r.getRow(), 1, 1, 9).getValues()[0]);
Logger.log("cslData: " + cslData)
if (cslData.length > 0) Logger.log("cslData[0]: " + cslData[0][0])
}
Reference:
findAll()

Searching for a value and if a value exists adding a row with copying the data in google spreadsheet

I want to search for a word in a certain column and then if the value exists, I want to copy the row below with its values and change the word to two different words.
My issue was in getting the found word row Number to insert a row below it.
function myFunction() {
var ss = SpreadsheetApp.openById("1fE404JUbw3aytlqtoht6FfrIhYhTpSe2MM5UDnBkFc4");
var sheet = ss.getSheets()[0]
let range = sheet.getDataRange();
var values = range.getValues();
var typeRange = sheet.getRange("E2:E");
var typeValues = typeRange.getValues();
var i;
for(i = 0; i <= lastRow ; i++){
for (let type of typeValues){
if (type == "Both"){
var bothRow = i+1;
}
//// var bothRow = sheet.getRange(i+1,1,1,typeValues[i].length);
//// ss.insertRowsAfter(bothRow, 1);
}
}
}
I have used alert to check and it inserted an infinite number of rows after row number 1.
function myFunction() {
let sheetui= SpreadsheetApp.getUi()
sheetui.createMenu("Rahaf Game")
.addItem("Stage 1", 'pop')
.addToUi();
// var ss = SpreadsheetApp.openById("1fE404JUbw3aytlqtoht6FfrIhYhTpSe2MM5UDnBkFc4");
// var sheet = ss.getSheets()[0]
// let range = sheet.getDataRange();
// var values = range.getValues();
// var typeRange = sheet.getRange("E2:E");
// var typeValues = typeRange.getValues();
}
function pop(){
//var ss = SpreadsheetApp.openById("1fE404JUbw3aytlqtoht6FfrIhYhTpSe2MM5UDnBkFc4");
var sheet = ss.getSheets()[0]
var typeRange = sheet.getRange("E2:E");
var typeValues = typeRange.getValues();
var lastRow = sheet.getLastRow();
var i;
for(i = 0; i <= lastRow ; i++){
for (let type of typeValues){
if (type == "Both"){
var bothRow = i+1;
ss.insertRowsAfter(bothRow, 1);
}
//// var bothRow = sheet.getRange(i+1,1,1,typeValues[i].length);
//// ss.insertRowsAfter(bothRow, 1);
}
}
}
Can someone please help in achieving the required result in inserting a row below and copy the values into it with changing the word into two words?
Answer:
.getValues() returns a 2D array of values and you are referencing it as if it is unidimentional.
Code fix:
You need to change your conditional such that the object you are checking is an array:
if (type == "Both"){
//code
}
Should be:
if (type == ["Both"]){
//code
}
Adding a row and copying the data to it:
You can add a row in a sheet after a given row with the insertRowsAfter() and use .getRange().setValues() to copy in the data:
for(var i = 0; i <= lastRow ; i++){
for (let type of typeValues){
if (type == ["Both"]){
var bothRow = i + 1;
ss.insertRowsAfter(bothRow, 1);
// increment lastRow as there is now one more row in the sheet
lastRow++;
// increment i as you want to skip the row you just copied!
i++;
var rangeToCopy = sheet.getRange(bothRow + ':' + bothRow).getValues();
sheet.getRange((bothRow + 1) + ':' + (bothRow + 1)).setValues(rangeToCopy);
}
}
}
Don't forget to increment lastRow by 1 if you add a new row so that your loop still reaches the end of the sheet.
References:
Class Range | Apps Script - Method getValues()
Related Questions:
Using Google Sheets scripts, why does my if statement always return false when comparing cell values?

Need a custom function to repeat for each row shown and minimize API calls

EDIT: I need help combining functions into 1 and adding a trigger.
In my spreadsheet I have rows 4-100 for customer service calls that are filtered from a "ServiceData" worksheet by either choosing a "Service Month" or "Service Day" (ie. "7/11" shows only 5 rows where "July" would show 65 rows) . Each row item has corresponding Place IDs for origin/destination in column K and L with an order # (as in 1st, 2nd, 3rd... service call of the day) in column J .
TravelTime spreadsheet
I'm using the following custom function travelTime() in cells M4:M100 to calculate driving duration and distance between 2 place IDs:
function travelTime(origin,destination) {
var API_KEY = PropertiesService.getScriptProperties().getProperty('keyMaps');
var baseUrl = "https://maps.googleapis.com/maps/api/distancematrix/json? units=imperial&origins=";
var queryUrl = baseUrl + "place_id:" + origin + "&destinations=" +
"place_id:" + destination + "&mode=driving" + "&key=" + API_KEY;
var response = UrlFetchApp.fetch(queryUrl);
var json = response.getContentText();
var time = JSON.parse(json);
return [[ time.rows[0].elements[0].duration.text,
time.rows[0].elements[0].distance.text ]] ;
A major issue is that many unnecessary service calls are being made to the API when I'm making edits to the "ServiceData" spreadsheet (ie. service date changes when a particular day is over-scheduled) and not needing the travel time updated until I'm done working through a schedule .
After researching quite a bit there seems to be several options I could be using; caching, looping, arrays, and putting everything into a script then attach to a button to only run when ready. Considering I'm a newbie, putting all these options together are definitely beyond my skill level and could really use some help.
EDIT with new functions:
So after more researching I have been able to put together the following functions that when each are run independently work great. Now the problem I'm having is putting these all together in particular adjusting the original travelTime()into newTravelTime(). I have made an attempt towards the right direction below but can't figure out how to get the API call in there .
function newTravelTime() {//<--**having issues how to write this function
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName("MonthlySA");
var sourceR = sourceSheet.getRange(4, 11, sourceSheet.getLastRow()-3, 4);
var sourceV = sourceR.getValues();
var array = [];
for (var i = 0; i < sourceV.length; i++) {
if (sourceV[i][2] == "") {
var origin = sourceV[i][0];//ori place IDs for API query
var destination = sourceV[i][1];//des place IDs API api query
}
array.push([sourceV[i][2]]);
}
sourceSheet.getRange(4, 13, array.length, 1).setValues(array);
I'd like to create a final getTravelTime() with all the functions and add an OnEdit trigger when either "Service Month" or "Service Day" changes in cells B1 or B2 to run them. If there is any advice with my functions themselves I would really appreciate some help, I am very new with this and trying.
///checks if origin/destination are already in the cacheSheet then return travel time to sourceSheet
function getCachedTravelTime() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName("MonthlySA");
var sourceR = sourceSheet.getRange(4, 11, sourceSheet.getLastRow()-3, 4);
var sourceV = sourceR.getValues();
var cacheSheet = ss.getSheetByName("TravelTimeCache");
var cacheR = cacheSheet.getRange(2, 1, cacheSheet.getLastRow()-1, 4);
var cacheV = cacheR.getValues();
var array = [];
for (var i = 0; i < sourceV.length; i++) {
for (var j = 0; j < cacheV.length; j++) {
//if origin/destination columns from sourceSheet match columns on cacheSheet
if (sourceV[i][0]+sourceV[i][1] == cacheV[j][0]+cacheV[j][1]) {
sourceV[i][2] = cacheV[j][2]; //column with travel duration
sourceV[i][3] = cacheV[j][3]; //column with travel distance
}
}
array.push([sourceV[i][2], sourceV[i][3]]);
}
sourceSheet.getRange(4, 13, array.length, 2).setValues(array);
}
///if origin or destination are blank, label as 'missing value'
function missingOD() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName("MonthlySA");
var sourceID = sourceSheet.getRange(4, 3, sourceSheet.getLastRow()-3, 12);
var sourceV = sourceID.getValues();
var array = [];
for (var i = 0; i < sourceV.length; i++) {
// if ID has a value
if (sourceV[i][0] != "") {
// if origin or destination is blank
if (sourceV[i][8] == "" || sourceV[i][9] == "") {
sourceV[i][10] = 'missing value';
}
}
array.push([sourceV[i][10]]);
}
sourceSheet.getRange(4, 13, array.length, 1).setValues(array);
}
///if cache not found - get the new travelTime for that origin/destination on sourceSheet...
function newTravelTime() {//<--
}
///...and store the new travelTime() in cacheSheet
function storeTravelTime() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName("MonthlySA");
var sourceR = sourceSheet.getRange(4, 11, sourceSheet.getLastRow()-3, 4);
var sourceV = sourceR.getValues();
var cacheSheet = ss.getSheetByName("TravelTimeCache");
var cacheR = cacheSheet.getRange(2, 1, cacheSheet.getLastRow()-1, 4);
var cacheV = cacheR.getValues();
var array = [];
for (var i = 0; i < sourceV.length; i++) {
var duplicate = false;
for (var j = 0; j < cacheV.length; j++) {
if (sourceV[i][0]+sourceV[i][1] == cacheV[j][0]+cacheV[j][1]) {
duplicate = true;
}
}
if(!duplicate){ //if origin/destination columns from sourceSheet are NOT matched on cacheSheet
array.push([sourceV[i][0], sourceV[i][1], sourceV[i][2], sourceV[i][3]]);//columns with new data
}
}
//add new data to last row of cacheSheet
cacheSheet.getRange(cacheSheet.getLastRow()+1, 1, array.length, 4).setValues(array);
}
One of the easiest solution thats coming to mind is Caching. Instead of making API calls everytime check if we have already made that call previously.
Something like this
function getTravelTime(origin, destination) {
var travelTime = getTravelTimeFromPreviousCall(origin, destination);
if (travelTime != null) {
return travelTime;
} else {
var travelTime = fetchTravelTime(origin, destination);
storeTravelTime(origin, destination, travelTime);
return travelTime;
}
}
function fetchTravelTime(origin, destination) {
var API_KEY = PropertiesService.getScriptProperties().getProperty('keyMaps');
var baseUrl = "https://maps.googleapis.com/maps/api/distancematrix/json? units=imperial&origins=";
var queryUrl = baseUrl + "place_id:" + origin + "&destinations=" + "place_id:" + destination + "&mode=driving" + "&key=" + API_KEY;
var response = UrlFetchApp.fetch(queryUrl);
var json = response.getContentText();
var time = JSON.parse(json);
return time.rows[0].elements[0].duration.text;
}
For this we can define our cache something like :
A sheet with column -
origin
destination
travel time
And we need to define following functions:
getTravelTimeFromPreviousCall(origin, destination) : In this we need to check cache and return travel time for that origin & destination, if not found then return null
storeTravelTime(origin, destination, time) : This will only store travel time for future use in cache sheet
You can try something like this :
function getTravelTimeFromPreviousCall(origin, destination) {
var cacheSheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(CACHE_SHEET_NAME);
var cacheData = sheet.getDataRange().getValues();
for (var i=0; i<cacheData.length; i++) {
if (cacheData[i][ORIGIN_COLUMN_INDEX]==origin && cacheData[i][DESTINATION_COLUMN_INDEX]==destination) {
return cacheData[i][TRAVEL_TIME_COLUMN_INDEX];
}
}
return null;
}
function storeTravelTime(origin, destination, travelTime) {
var cacheSheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(CACHE_SHEET_NAME);
sheet.appendRow([origin, destination, travelTime]);
}
Please fix loop variable, array indexes & constants, as per your sheet.

Google Scripts For Loop

I'm trying to insert some data from a spreadsheet into a different spreadsheet, the problem is that the loop is not behaving as expected, it only gives me one entry in the target spreadsheet. I've tried using while and no function but it didn't work.
Here is the code:
function move(){
var homeBook = SpreadsheetApp.getActiveSpreadsheet();
var sheet = homeBook.getSheets()[0];
var limit = sheet.getLastRow(); //number of rows in the sheet
var evento = sheet.getRange(2, 1, limit-1).getValues(); //event list
var descript = sheet.getRange(2,2,limit-1).getValues(); //description list
var tags = sheet.getRange(2,3,limit-1).getValues(); //tag list
var sheetsIDHome = sheet.getRange(2,4,limit-1).getValues(); //ID list
var targetBook = SpreadsheetApp.openById("1t3qMTu2opYffLmFfTuIbV6BrwsDe9iLHZJ_ZT89kHr8"); //target workbook
var target = targetBook.getSheets()[0]; //Sheet1
var targetLimit =target.getLastRow(); //Rows with content
var sheetsIDTarget = target.getRange(targetLimit, 4); // ID list
var targetRow = targetLimit+1; //row where content is going to be inserted
for(i = 2;i <= limit;i++){//loop for each value to be inserted in each row of the target sheet
(function(x){
target.getRange(targetRow,1).setValue(x);
target.getRange(targetRow,2).setValue(descript[2]);
target.getRange(targetRow,3).setValue(tags[3]);
target.getRange(targetRow,4).setValue(sheetsIDHome[4]);
targetRow = targetRow++;
})(i);
};
You're trying to access the four arrays created from the values for columns 1-4.
Your for statement needs to match their structure, starting with the first array instance of 0. You can use any of the arrays for the iteration, I've chosen the first.
In addition, I've removed the function and replaced x with the instance from evento.
++ increments the value of the variable, no need for assignment there.
for (var i = 0; i < evento.length; i++) {
target.getRange(targetRow,1).setValue(evento[i]);
target.getRange(targetRow,2).setValue(descript[i]);
target.getRange(targetRow,3).setValue(tags[i]);
target.getRange(targetRow,4).setValue(sheetsIDHome[i]);
targetRow++;
}
there is a code that has been mis-placed, kindly check my code below:
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=2;i<=lastRow;i++){
var dataID = testTable.getRange(i,1).getValue();
if(testFormValue == dataID)
{
testTable.getRange(i,6).setValue("new value");
};
};
};
hope this helps

Cannot convert class to object in spreadsheet

I wish to get a couple of properties of all the folders in my GDrive and write these properties to a spreadsheet. Because of the large number of folders (over 300) I have decided to use Paging and Batch processing. This seems to be working but I can't seem to write the Array[][] I've created in the batch processing to the spreadsheet.
I'm am getting the following error when I try to set the values in my spreadsheet:
Cannot convert (class)#3cc8188e to Object[][].
I did not find any listed questions that were similar to my problem.
The last line of the script is highlighted when the error appears. Code is:
function myFunction() {
var folder = DocsList.getFolder('MyFolder');
var subfolders = null;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var cell = sheet.getRange('a1');
var x = 0;
var pageSize = 250;
var token = null;
var xfolders = new Array(500);
do {
var resultset = folder.getFoldersForPaging(pageSize, token);
subfolders = resultset.getFolders();
token = resultset.getToken();
x = subfolders.length;
for (var a = 0; a < subfolders.length; a++) {
var contents = subfolders[a].getFiles();
xfolders[a] = new Array(6);
if(contents.length>0) {
xfolders[a][0] = subfolders[a].getName();
xfolders[a][1] = subfolders[a].getDateCreated();
xfolders[a][2] = subfolders[a].getLastUpdated();
xfolders[a][3] = contents.length;
xfolders[a][4] = subfolders[a].getSize();
xfolders[a][5] = a;
}
}
} while (subfolders.length > pageSize)
sheet.getRange(1, 1, x, 6).setValues(xfolders);
}
You've started with an array xfolders for which you've initialized a length. There's no need to do this - it's enough to know that it's an array. Later you call getRange() with rows==x, where x = subfolders.length... and if that isn't 500 (the length of xfolders) you will end up with your error. (...because you have undefined array elements.)
What you need to do is make sure that the range you're writing to has the same dimensions as the values you're presenting. One way to do that is to calculate the range dimensions using xfolders itself.
Here's your function, refactored to grow xfolders dynamically, and then to use its dimensions for output to the spreadsheet.
function myFunction() {
var folder = DocsList.getFolder('MyFolder');
var subfolders = null;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var cell = sheet.getRange('a1');
var pageSize = 250;
var token = null;
var xfolders = [];
do {
var resultset = folder.getFoldersForPaging(pageSize, token);
subfolders = resultset.getFolders();
token = resultset.getToken();
for (var a in subfolders) {
var contents = subfolders[a].getFiles();
xfolders[a] = [];
if(contents.length>0) {
xfolders[a][0] = subfolders[a].getName();
xfolders[a][1] = subfolders[a].getDateCreated();
xfolders[a][2] = subfolders[a].getLastUpdated();
xfolders[a][3] = contents.length;
xfolders[a][4] = subfolders[a].getSize();
xfolders[a][5] = a;
}
}
} while (subfolders.length == pageSize) // Quit when we aren't getting enough folders
sheet.getRange(1, 1, xfolders.length, xfolders[0].length).setValues(xfolders);
}

Resources