GSheet Script: How to Optimize my Row and Sheet Iterator - for-loop

Long story short, I have this bit of Google Script that clears content automatically in a GSheet. It is set on a trigger and it works...the code does what it's supposed to do. The issue is that it runs slow. It takes 2 to 3 minutes for the iterator to run. To help you scope the size of the task: there is 150 rows on each of the 8 sheets.
The objective of the code is to clear a number of rows on each sheet based on the value of the cell in the first column of a row.
So I would like to know if anyone has any insight or suggestion to improve the running time. I understand my method of using a for loop checks rows one by one, and that's a time-consuming task. I couldn't think of an alternate method with arrays or something?
Thanks all!
Here's the code:
function Reset_Button() {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i = 1; i < sheets.length ; i++ ) {
var sheet = sheets[i];
sheet.getRange("C2").setValue(new Date());
var rangeData = sheet.getDataRange();
var lastRow = rangeData.getLastRow();
var searchRange = sheet.getRange(1,1, lastRow, 1);
for ( j = 1 ; j < lastRow ; j++){
var value = sheet.getRange(j,1).getValue()
if(value === 0){
sheet.getRange(j,2,1,5).clearContent()
}}}}

Typically you want to do as few writes to the spreadsheet as possible. Currently your code goes through each line and edits it if necessary. Instead get the entire data range you will be working with into one variable (let's say dRange and use .getValues() to get a 2d array of all the values into a second variable (let's say dValues). Then simply iterate over dValues, setting a blank "" in each you want to clear. Once you are done going over all values, just do a dRange.setValues(dValues) (that's why I said to keep the range in a separate variable). So as an example, the following will clear columns B through F if column A has a 0
function test(){
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i = 1; i <sheets.length; i++) {
sheets[i].getRange("C2").setValue(new Date());
var dRange = sheets[i].getDataRange();
var dValues = dRange.getValues();
for (var j = 1; j < dRange.getLastRow(); j++){
if (dValues[j][0] == 0) {
for (var c = 1; c < 6; c++) {
dValues[j][c] = ""
}
}
}
dRange.setValues(dValues);
}
}
For a single sheet of ~170 rows this takes a few seconds. One thing to note is that I wrote it based on your script, you set a date value in C2 however in your sript (and thus in the one I wrote based on yours) that falls within the range you are checking to be cleared, so double check your ranges

Related

For loop not looping through spreadsheet

I made a small search box to retrieve values from a datasheet. For some reason it only loops once. Also this is the first time I'm trying something in google sheets, so if I want to search a string in column B, then the SEARCH_COL_IDX should be 1 right?
I put in a few loggers to check the process. I do see Logger.log(str), Logger.log("Check") and Logger.log("Check2). The latter two only once, where it should be multiple times imo. The Logger.log(row[#]) I don't get to see at all, which means it doesn't find anything.
I also tried doing if(row[SEARCH_COL_IDX] = str) { to see where it is looking. Than the Logger.log(row[#]) do come back, but from different rows AND columns. I am doing something wrong, but I can't find it.. any suggestions?
var SPREADSHEET_NAME = "Database";
var SEARCH_COL_IDX = 1;
var RETURN_COL_IDX= 1;
function searchStr(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName("Userform");
var str = formSS.getRange("d17").getValue();
Logger.log(str);
var values = ss.getSheetByName("Database").getDataRange().getValues();
for (var i = 0; i < values.length; i++) {
Logger.log("check");
var row = values[i];
Logger.log("check2");
if(row[SEARCH_COL_IDX] == str) {
formSS.getRange("d3").setValue(row[0]);
Logger.log(row[0]);
formSS.getRange("d5").setValue(row[1]);
Logger.log(row[1]);
formSS.getRange("d7").setValue(row[2]);
Logger.log(row[2]);
formSS.getRange("d9").setValue(row[3]);
Logger.log(row[3]);
formSS.getRange("d11").setValue(row[4]);
Logger.log(row[4]);
formSS.getRange("d13").setValue(row[5]);
Logger.log(row[5]);
}
Logger.log("nothing found");
return row[RETURN_COL_IDX];
}
Your function has a return command in the last line of your loop block.

How do I wait for value to be set in a cypress?

I looping through a table and looping through the data of those table in order to compare them. However, I am trying to use break if one of the table data doesn't match to prevent further checking.
I run into a problem here because the break is going to happen inside cy.then, which doesn't work. So now I am trying to find a way to allow the cy.then to set the value then proceed to synchronously check everything else.
Here is what I have tried so far to no avail
cy...then((body)=>{
for (let row = 0; row < body.length; row++) {
//Body is the table, and we get the table len
for (let col = 0; col < 4; col++) {
//Begin looping thru each row's col and compare
// Here I attempt to create sync code because I want to not continue
// to loop thru all 4 col if one doesnt match
let val;
new Cypress.Promise((resolve)=>{
cy.get('...').eq(row)...eq(col).then((txt)=>{
val = txt
resolve(txt
})
})
//Begin to compare using val and break child loop if needed
}
}
})
I keep getting val as undefined. I am new to cypress so any pointers would be cool.
Thanks.
What I have tried pt2:
Using async/await in at
cy...then(async(body)
and awaiting the cypress promise, gets a timeout 4000ms error, increased timeout to 10000ms and still timed out.
Edit: According to your answer, if your code is correct, you can use a should instead of then, because of possible retriability:
cy...should((body)=>{ //over here
for (let row = 0; row < body.length; row++) {
//Body is the table, and we get the table len
for (let col = 0; col < 4; col++) {
//Begin looping thru each row's col and compare
// Here I attempt to create sync code because I want to not continue
// to loop thru all 4 col if one doesnt match
let val;
new Cypress.Promise((resolve)=>{
cy.get('...').eq(row)...eq(col).should((txt)=>{ //or over here
val = txt
resolve(txt
})
})
//Begin to compare using val and break child loop if needed
}
}
})

Apps Script Exceeded MAXIMUM_RUNNING_TIME Workaround

First of all, I am continuing an old thread at this link that I am unable to comment on due to being a newbie.
I have a situation that an answer in that thread given by user Br. Sayan would really improve my Spreadsheet Google App Script. I am making calls to Google Url Shortener API, which puts quotas at 1 call per user per second. I have slowed my script down enough to accommodate this quota, but I then I run over the MAX_RUNNING_TIME for App Scripts execution due to the extended number of calls I need to make, so I need to break the loop when the execution time is exceeded and pick up where I left off.
Here is the code of his answer:
function runMe() {
var startTime= (new Date()).getTime();
//do some work here
var scriptProperties = PropertiesService.getScriptProperties();
var startRow= scriptProperties.getProperty('start_row');
for(var ii = startRow; ii <= size; ii++) {
var currTime = (new Date()).getTime();
if(currTime - startTime >= MAX_RUNNING_TIME) {
scriptProperties.setProperty("start_row", ii);
ScriptApp.newTrigger("runMe")
.timeBased()
.at(new Date(currTime+REASONABLE_TIME_TO_WAIT))
.create();
break;
} else {
doSomeWork();
}
}
//do some more work here
}
My Questions:
Is MAX_RUNNING_TIME a global variable with a value set by Apps Script that I can leave that reference as-is, or must I replace it with a value equalling the 6 minutes listed as the quota for run time on the Google API Console?
How can I place the bulk of my function within this script so that a loop that runs inside my function (say var i = 0; i < data.length; i++) will be synchronized with the loop in the portion given in the above code?
Clarification: when i is incremented up by 1, I need ii to increment by 1.
Does this happen automatically? Do I need one loop nested inside the other? Does the bulk of my function go in the first '//do some work here' or the second '//do some work here' or possibly even doSomeWork()?
#tehhowch agreed! However, HOW I need to adapt my code depends on where I need to put it in the above snippet.
Here is what I have so far:
'function short() {
var = startTime = (new Date()).getTime();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var run = 0;
var finc = 50;
var istart = run * finc;
var iLen = (run + 1) * finc;
var startRow = 2 + istart;
var endRow = startRow + finc;
var data = sheet.getSheetValues(startRow,2,endRow,1);
var shortUrl = new Array();
for (var i=istart; i < iLen; i++) {
Utilities.sleep(1100);
var url = UrlShortener.Url.insert({longUrl: data[i][0]});
shortUrl.push([url.id]);
Logger.log([url.id]);
}
var t = ss.setActiveSheet(ss.getSheets()[0]);
t.getRange(startRow,4,finc,data[0].length).clearContent();
t.getRange(startRow,4,finc,data[0].length).setValues(shortUrl);'
So if I update the code after each subsequent run to manually increase the variable 'run' by 1, and manually run the code again, this works.
I have also tried break it down into multiple functions by updating the i= and i < parts for each subsequent function, which also works, but requires much more manual work.
I have also tried, unsuccessfully, to use a prompt with a button press that continues the function, which would be better than the other attempts, but would still require a button press to resume the code after each run.
I want to automate the function as much as possible.

Google Sheets, Delete 2 or more cells data if another cell has X in it

I have a work log that needs to be cleared at end of day but only if a packer has printed. The department sheets have 2 cells per row, A & G, that need the data cleared if cell H has an X in it. The "Main" sheet is also going to need 4 cells cleared and a formula reset too but if I see the script for the department sheets, I think I will be able to adjust it for the Main sheet.
https://docs.google.com/spreadsheets/d/1L-SK-dWUW_EyX1KxHZLrY_wf-SlxrgvW8tQo5q8ijj4/edit?usp=sharing
I have found a few scripts that will delete the entire row but that's not what I need.
This one I couldn't get to work at all:
function checkH_bb() {
var s, data, colH, colA, key, i;
s=SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Dept 500");
data=s.getDataRange().getValues();
for (i in [1,2,3,4,5,6,7,8,9,10]) data.shift();
colH=data.map(function(x) {return x[7];});
colA=data.map(function(x) {return x[0];});
key="x";
for (i in colH) {
if (colH[i]==key) colA[i]='';
}
s.getRange(11, 1, colA.length).setValues(colA.map(function(x) {return [x];}));
}
This one I didn't try because I realized I needed to keep the formulas.
function readRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var rowsDeleted = 0;
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (row[1] == 'old') {
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
};
There are formulas in other cells that I need to keep. I looked into keeping the formulas if the data is deleted but I couldn't get that to work either.
This one I couldn't get to work, not sure why.
function myFunction() {
function clearValues(range) {
var formulas = range.getFormulas();
range.clearContent();
range.setFormulas(formulas);
}
}

"for" loop for Google Spreadsheet values not looping

I'm currently writing a statistic spreadsheet script for my guild, which reads out the class of one person and counts it on the statistic sheet.
For some reason the for loops aren't working. When I execute the script, it does nothing. Everything before the for loop seems to work. I have used the debugger, and set a debug point from the point of the for loop and the window is opening and closing after like 1 second.
This is my code as of now:
function addToStatistik() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var source_sheet = spreadsheet.getSheetByName("Raid Montag");
var source_range_names = source_sheet.getRange("C4:C13");
var source_range_setup_boss1 = source_sheet.getRange("M4:M13");
var target_sheet = spreadsheet.getSheetByName("Statistik");
var target_range_names = target_sheet.getRange("A4:A31");
var target_range_boss1 = target_sheet.getRange("K4:S31");
target_sheet.getRange(2,1).setValue("Debug1"); //testing stuff
for (var i=0; i < source_range_names.length; i++) {
for (var j=0; j < target_range_names.length; j++) {
if (source_range_names[i][0] == target_range_names[j][0]) {
if (source_range_setup_boss1[i][0].indexOf("War") > -1) {
target_sheet.getRange(9,5).setValue("TEST");
}
}
}
}
}
Someone can find any errors in there? I can't find anything and google also isnt helping me.
You are getting the range, but not the values. This line:
var source_range_names = source_sheet.getRange("C4:C13");
gets a range, but not any values.
Should be:
var source_range_names = source_sheet.getRange("C4:C13").getValues();
The outer loop never loops. There is no length of a range.
for (var i=0; i < source_range_names.length; i++) {
You don't need to change the above line, but currently the variable source_range_names is a range, and not a 2D array of values.
Before you iterate you need to get the values of the range, to achieve this you need to use the method getValues() or getDisplayValues():
function leFunction() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var source_sheet = spreadsheet.getSheetByName("Raid Montag");
var source_range_names = source_sheet.getRange("A1:C13");
var values_range_names = source_range_names.getDisplayValues();
Logger.log(values_range_names);
for (var i=0; i < values_range_names.length; i++) {
// Do Something
}
}

Resources