Faster Iterative Calculation - performance

I've made a script that performs an iterative calculation to find values based on a gross percentage of project cost.
This is what I have so far:
//<!-- FEE, BOND, & INSURANCE CALCULATOR --!>
function feeBondInsCalculator (){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Pretty Sum Form");
sheet.activate();
// Get all variables from named ranges
var bondValue = ss.getRangeByName("bondValue");
var bondCalc = ss.getRangeByName("bondCalc");
var insValue = ss.getRangeByName("insValue");
var insCalc = ss.getRangeByName("insCalc");
var feeValue = ss.getRangeByName("feeValue");
var feeCalc = ss.getRangeByName("feeCalc");
var interCount = ss.getRangeByName("interCount")
// Interative calculation to paste calculated values into the body of the spreadsheet
for (var i = 0; i<11; i++){
feeCalc.copyTo(ss.getRangeByName("feeValue"), {contentsOnly: true});
bondCalc.copyTo(ss.getRangeByName("bondValue"), {contentsOnly: true});
insCalc.copyTo(ss.getRangeByName("insValue"), {contentsOnly: true});
interCount.setValue(i)
}
}
I used range names so that users can add/delete rows and columns and not have to reset the code. When executed, the code works fine, but takes about two seconds per iteration. Is there a more efficient way to make this work?

Instead of using copy, you might try referencing your ranges directly in a formula like:
=arrayformula(if(bondValue <>"",bondValue,""))

Related

How to reduce time of script execution with FormApp...getResponses()?

The Email collection function built into the form is in the Text format. We have a lot of employees filling out this form, so it would be more convenient to choose from a drop-down list than to manually enter an email for each employee. The built-in function of collecting emails does not allow using the Dropdown list question type, so I had to disable it and send the response to the email using a script.
In addition, when you select one of the options (Transfer) in the form, you need to insert an additional correction row with data. This is also done by the script.
The script works fine, but unfortunately, its execution time reaches 45 seconds. I think this is abnormally large.
Updated code:
var FORM_ID = '#####';
var SHEET_NAME = 'Operations';
function sendFormToEmail() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
console.time('section10'); // 25579ms
var sheet = ss.getSheetByName(SHEET_NAME);
console.timeEnd('section10');
// Open a form by ID and log the responses to each question.
var form = FormApp.openById(FORM_ID);
var formResponses = form.getResponses();
var i = formResponses.length - 1;
var formResponse = formResponses[i]; // Last item
var itemResponses = formResponse.getItemResponses();
var length = itemResponses.length;
Logger.log("length = " + length);
var emailTo = itemResponses[0].getResponse(); // Returns the email if given
var cp = itemResponses[1].getResponse(); // Counterparty
var subject = "Input form: "+ cp;
var datePay = itemResponses[2].getResponse(); // Date of the operation
var dateAccept = itemResponses[3].getResponse(); // Date of acceptance
var sum = itemResponses[4].getResponse() ; // Amount
var what = itemResponses[5].getResponse(); // Operation type
var comm = "Correction " + itemResponses[length - 1].getResponse(); // Last response
var sum_1 = parseFloat(sum.replace(/,/, '.')) * (-1); // Amount * (-1)
var timestamp = new Date();
var textBody = "Operation: " + timestamp + ";\n";
for (var j = 0; j < itemResponses.length; j++) {
var itemResponse = itemResponses[j];
var resp = itemResponse.getResponse();
textBody += itemResponse.getItem().getTitle() + "=" + resp + ";\n";
}
if (what == 'Transfer') { // If the transfer between your accounts
var lr = sheet.getLastRow();
sheet.insertRowBefore(lr);
var values = [datePay, dateAccept, "", sum_1, "", "", "", "", comm, "", "", "", what, timestamp, "", "", ""];
Logger.log(values);
console.time('section12'); // 19449ms
sheet.getRange(lr, 2, 1, 17).setValues([values]);
console.timeEnd('section12')
}
if(emailTo !== undefined){
GmailApp.sendEmail(emailTo, subject, textBody);
}
}
At first I thought it was the large size of the acceptance sheet (about 13 thousand rows). I created a copy of the table, reduced it to several dozen rows, but the speed did not increase much.
Then I deleted the answers from the form (there were just under 9000 of them) - the same thing, I didn't get much performance gain.
Anyone have any ideas how to change the script algorithm to improve its performance?
===
Conclusions
Thanks to #TheMaster for the help with console.time(). Thanks to this tool I found bottlenecks in the code. More precisely, the code works well, but it's about the structure of the spreadsheets system with which the code interacts. The structure needs to be optimized.
There was also an idea addressed, probably, to Google developers. It would be great if there was a tool that visually (graphically) displays the relationships between spreadsheets and sheets that make up a single system. Perhaps with some kind of numerical characteristics that reflect, for example, the interaction time between its blocks. This would make it possible to quickly eliminate such bottlenecks in the system and improve it.
Investigation:
Pinpointing the issue is done using console.time() and console.timeEnd() of each section of code and calculating the time taken by reachl each section of code.
Issue:
As discussed in the question comment chain, This is due to a bloated spreadsheet with many sheets and import formulas. This caused more time to get the exact sheet and to use setValues:
console.time('section10'); // 25579ms
var sheet = ss.getSheetByName(SHEET_NAME);
console.timeEnd('section10');
//....
console.time('section12'); // 19449ms
sheet.getRange(lr, 2, 1, 17).setValues([values]);
console.timeEnd('section12')
Possible solutions:
Reduce number of sheets
Reduce interconnected spreadsheets (=import* formulas)
Delete empty rows on the bottom and empty columns on the right of each sheet.

Google Apps Script For Loop To Grab Cell Values and Insert into Formula

I have 5 possible locations where a user can enter either: "Yes, No, or any URL". If a URL is entered, I need the value of that cell (the URL) to be entered into an equation on a corresponding cell in a separate sheet. Here is what I have:
//Locations of the cells where the user can enter the URL.
var graphic1_loc = 'A62';
var graphic2_loc = 'A63';
var graphic3_loc = 'A64';
var graphic4_loc = 'A65';
var graphic5_loc = 'A66';
//The corresponding locations on the "Briefing" sheet where the images would get inserted.
var graphic1_placement = 'B45';
var graphic2_placement = 'B46';
var graphic3_placement = 'B47';
var graphic4_placement = 'B48';
var graphic5_placement = 'B49';
//main_gen is the name of the sheet where the user enters the data. This just grabs the value
//the user entered and stores it in the corresponding variable.
var graphic1 = SpreadsheetApp.getActiveSheet().getRange('main_gen!'+graphic1_loc).getValue();
var graphic2 = SpreadsheetApp.getActiveSheet().getRange('main_gen!'+graphic2_loc).getValue();
var graphic3 = SpreadsheetApp.getActiveSheet().getRange('main_gen!'+graphic3_loc).getValue();
var graphic4 = SpreadsheetApp.getActiveSheet().getRange('main_gen!'+graphic4_loc).getValue();
var graphic5 = SpreadsheetApp.getActiveSheet().getRange('main_gen!'+graphic5_loc).getValue();
var graphics_placements = ["B45", "B46", "B47", "B48", "B49"]; //These are the corresponding cells where the image would be placed on a separate sheet.
//If any graphic is a URL, insert that image into it's corresponding cell in the Briefing tab.
for (var num = 0; num < 5; num ++) {
var graphicformula = '=IMAGE("' + graphic[num] + '",1)';
if (graphic[num].length > 5) {
SpreadsheetApp.getActiveSheet().getRange('Briefing!'+graphic[num]_placement).setFormula(graphicformula);
}
}
What I am trying to get the If statement within the For Loop to say is...If the length of the value of graphic1 is > 5 (if it isn't yes (3) or no (2) then it is assumed it is a URL) then find the corresponding cell on the "Briefing" sheet to insert the formula. The current way I have it written is not correct, but I wanted to attempt to write something to give you an idea of what I am after. Thanks for any help you can provide!
You want to run SpreadsheetApp.getActiveSheet().getRange('Briefing!'+graphic[num]_placement).setFormula(graphicformula); when the value of graphic# is URL.
For example, when the URL is in the cell A63 in the sheet main_gen, you want to put the formula of =IMAGE(URL,1) to the cell B46 in the sheet Briefing
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Modification points:
In your script, you are trying to use graphic[num] as a variable of graphic1, graphic2,,,. In this case, this cannot be used as the variable.
In your situation, the cells A62:A66 in the sheet main_gen are correspoinding to the cells B45:B49 in the sheet Briefing. I think that this can be used for modifying your script.
The values of the cells A62:A66 in the sheet main_gen can be retrieved by ss.getSheetByName("main_gen").getRange("A62:A66").getValues().
In order to confirm whether the value of cell is the URL, in this modification, I used test().
I think that when the destination sheet is declared out of the for loop, the process cost can be reduced.
When above points are reflected to your script, it becomes as follows.
Modified script:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheetByName("main_gen").getRange("A62:A66").getValues();
var dstSheet = ss.getSheetByName("Briefing");
var graphics_placements = ["B45", "B46", "B47", "B48", "B49"];
for (var num = 0; num < source.length; num ++) {
if (/https?:\/\//.test(source[num][0])) {
var graphicformula = '=IMAGE("' + source[num][0] + '",1)';
dstSheet.getRange(graphics_placements[num]).setFormula(graphicformula);
}
}
References:
test()
getValues()
If I misunderstood your question and this was not the direction you want, I apologize.

optimise Google Apps script

Could anyone please help me optimise this Google Sheets script?
It's painfully slow. It barely makes 300 copies in 24 hours (I need the Drive API to make the copies as "view only users can't copy/print/download")
function addCopies() {
var count = 10;
var input = DriveApp.getFileById('1GQI5m0g5XsCWi1dOTBWFs3VJkzW7e8N__09C3-2BEGI')
var results = DriveApp.getFileById('1gkOCTPJuqTQnNNlaE3jD2Hq2COU-lO02PmlO_xMepx0')
var indexSH = SpreadsheetApp.openById('1o6ZfweVylhnDXoVc9u_R8afKdVfXYgtdqtj_lSCdFEk').getSheetByName('Index')
for(var i=0;i<count;i++)
{
//make copies of both files (input & results)
var inputCopy = input.makeCopy(input.getName(), DriveApp.getFolderById('0B0go-fsdFA3ISG0xNGlhTDZoM0U'))
inputCopy.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.EDIT)
var inputID = inputCopy.getId()
//make results to be restricted using API
var resultsCopy = results.makeCopy(results.getName(), DriveApp.getFolderById('0B0go-fsdFA3ISG0xNGlhTDZoM0U'))
resultsCopy.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW)
var resultsID = resultsCopy.getId()
var apiFile = DriveAPI.Files.get(resultsID)
apiFile.labels.restricted = true
DriveAPI.Files.update(apiFile, resultsID)
//save IDs of both files to an index for later re-use
var lastRow = indexSH.getRange(1,1).getValue()
var addRng = indexSH.getRange(lastRow+1, 1,1,3)
addRng.setValues([['free',inputID,resultsID]])
}
}
Edit: techydesigner said "we don't optimise code on SO" . So please let me rephrase : am I using the wrong Drive services ? Is there a method to make those copies faster ?

Is there a way to mass input data validation in google sheets

I'm trying to create a drop down menu with contents based on a another cell in the same row. For example if A1 = 'yes' then the drop down in B2 gives you the options of 'yes' or 'no'. I can do this I have the list data set up and to code works. The problem is I need to do this 155 times in 4 different sheets. Is there a faster way to do this than right clicking and editing the data validation rules for each cell. Here's a link to the test sheet I'm working on :
https://docs.google.com/spreadsheets/d/1rd_Ig_wpof9R_L0IiA1aZ9syO7BWxb6jvBhPqG8Jmm4/edit?usp=sharing
You can set data validation rules with a script, as documented here. Here's a reference for starting with Apps scripts.
I wrote a function that does approximately what you described. It works with the range B3:B157 of the sheet '9th grade' in the current spreadsheet. For each of them, it sets the validation rule to be: a value in the same row, columns B and C of sheet 'List Data'. The line with
....... = listData.getRange(i+3, 2, 1, 2);
will need to be modified if the source range of validation is to be different. Here, the parameters are: starting row, starting column, number of rows, number of columns. So, 2 columns starting with the second, in row numbered i+3.
function setRules() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var grade = ss.getSheetByName('9th Grade');
var listData = ss.getSheetByName('List Data');
var range = grade.getRange('B3:B157');
var rules = range.getDataValidations();
for (var i = 0; i < rules.length; i++) {
var sourceRange = listData.getRange(i+3, 2, 1, 2);
rules[i][0] = SpreadsheetApp.newDataValidation().requireValueInRange(sourceRange).build();
}
range.setDataValidations(rules);
}
I land in this issue for a diferent reason: "Just mass DataValidation copy (or update) in one column". Thanks, to user3717023 that bring me a light.
I hope that helps someone this simplification.
function setRules() {
//select spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var leads = ss.getSheetByName('Leads');
//Select correct Datavalidation
var rangeNewDataValidation = leads.getRange('M2:M2');
var rule = rangeNewDataValidation.getDataValidations();
//Copy (or Update) Datavalidation in a specific (13 or 'M') column
var newRule = rule[0][0].copy();
Logger.log(leads.getMaxRows())
for( var i=3; i <= leads.getMaxRows(); i++){
var range = leads.getRange(i, 13);
range.setDataValidations([[newRule.build()]]);
}
}

Import random columns from spreadsheet into order in new spreadsheet

I'm trying to code a script that imports specific columns for a spreadsheet to another spreadsheet. However, currently i'm writing/reading every column separately, which takes quite a while. I do this because I don't need all the columns from the original sheet into the new sheet. I was wondering if my current script could be changed to get to a script where you once read the necessary columns and write them all together? I tried to search for the same but the different order of the columns and the need for specific columns makes it hard.
I hope someone could help me get to the answer.
Thanks in advance,
Tim
function getPDdataupper() {
var sheet = SpreadsheetApp.getActive().getSheetByName('SPR');
var Num = Browser.inputBox("What URL are do you want to copy from?");
var ssraw = SpreadsheetApp.openById(Num);//put Address in here*****
var sheetraw = ssraw.getSheetByName("SPR");
sheet.getRange(10,11,(sheet.getLastRow())-9,3).clearContent();
sheet.getRange(10,1,(sheet.getLastRow())-9,3).clearContent();
//ADJUST ROW
var lastrow = sheet.getLastRow()-10;
var lastrowcopiedsheet = sheetraw.getLastRow()-10;
var diff = lastrowcopiedsheet - lastrow;
if(diff > 0){sheet.insertRowsAfter(11,diff);}
if(diff < 0){sheet.deleteRows(20,(diff*-1)); }
sheet.getRange(11,1,(sheet.getLastRow())-11,33).clearContent();
var values = sheetraw.getRange(1,1,5,1).getValues();
sheet.getRange(1,1,5,1).setValues(values);
var values = sheetraw.getRange(10,3,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,2,(sheet.getLastRow()-9),1).setValues(values);
var values = sheetraw.getRange(10,38,sheetraw.getLastRow()-9,2).getValues();
sheet.getRange(10,3,(sheet.getLastRow()-9),2).setValues(values);
var values = sheetraw.getRange(10,43,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,6,(sheet.getLastRow()-9),1).setValues(values);
var values = sheetraw.getRange(10,44,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,5,(sheet.getLastRow()-9),1).setValues(values);
var values = sheetraw.getRange(10,6,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,7,(sheet.getLastRow()-9),1).setValues(values);
var values = sheetraw.getRange(10,54,sheetraw.getLastRow()-9,2).getValues();
sheet.getRange(10,8,(sheet.getLastRow()-9),2).setValues(values);
var values = sheetraw.getRange(10,58,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,10,(sheet.getLastRow()-9),1).setValues(values);
var values = sheetraw.getRange(10,14,sheetraw.getLastRow()-9,19).getValues();
sheet.getRange(10,14,(sheet.getLastRow()-9),19).setValues(values);
var values = sheetraw.getRange(10,51,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,33,(sheet.getLastRow()-9),1).setValues(values);
var values = sheetraw.getRange(10,62,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,34,(sheet.getLastRow()-9),1).setValues(values);
var values = sheetraw.getRange(10,4,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,35,(sheet.getLastRow()-9),1).setValues(values);
I have sorted it myself now. For someone who is looking for the same thing, please see the code below. If anyone else thinks of something better please feel free to amend.
function getPDdataupper() {
var sheet = SpreadsheetApp.getActive().getSheetByName('SPR');
var folder= DriveApp.getFolderById('FolderID');
var files = folder.getFilesByType(MimeType.GOOGLE_SHEETS);
while (files.hasNext()) {
var ssraw = SpreadsheetApp.open(files.next());
//var ssraw = SpreadsheetApp.open(Num);
}
sheet.getRange(11,1,(sheet.getLastRow())-10,53).clearContent();
var sheetraw = ssraw.getSheetByName("SPR");
sheet.getRange(10,11,(sheet.getLastRow())-9,3).clearContent();
sheet.getRange(10,1,(sheet.getLastRow())-9,3).clearContent();
//ADJUST ROW
var lastrow = sheet.getLastRow()-10;
var lastrowcopiedsheet = sheetraw.getLastRow()-10;
var diff = lastrowcopiedsheet - lastrow;
if(diff > 0){sheet.insertRowsAfter(11,diff);}
if(diff < 0){sheet.deleteRows(20,(diff*-1)); }
//var values = sheetraw.getRange(10,("C10:C,AL10:AM,AQ10:AR,F10:F,BB10:BC,BF10:BF"),(sheetraw.getLastRow()-9),53).getValues();
//sheet.getRange(10,2,(sheet.getLastRow()-9),(sheet.getLastColumn()-1)).setValues(values);
var data = sheetraw.getDataRange().getValues();{
var target = new Array();
for(n=0;n<data.length;++n){
target.push( [[data[n][2]], [data[n][37]], [data[n][38]], [data[n][43]], [data[n][42]], [data[n][5]], [data[n][53]], [data[n][54]], [data[n][57]]] );
}
sheet.getRange(1,2,target.length,target[0].length).setValues(target);
}

Resources