Google script timeout - javascript

I've been using a macro in an Excel for some years and wanted to translate it in a google script to collaborate in Drive.
I'm using a two sheets setup (one named "BILAN" which is the overview and one named INPUT for entering data. The script works just fine while there is not too much inputs, but I'm expecting to reach near a thousand inputs by the end of the file's use.
Basically, the script is a double loop to summarize the inputs in the BILAN sheet. Thanks in advance for your help !!
Here's the code I'm using :
function getTransportDates() {
var ss = SpreadsheetApp.getActive();
var strDatesTransport = '';
const intNbClients = ss.getSheetByName('BILAN').getDataRange().getLastRow();
const intNbInputs = ss.getSheetByName('INPUT').getDataRange().getLastRow();
for (let i = 4; i <= intNbClients; i++) { // loop through the addresses in BILAN
if (ss.getSheetByName('BILAN').getRange(i, 9).getValue() >0) {
for (let j = 4; j <= intNbInputs; j++) { // loop through the adresses in INPUT
if (ss.getSheetByName('INPUT').getRange(j, 2).getValue() == ss.getSheetByName('BILAN').getRange(i, 1).getValue()) {
strDatesTransport = strDatesTransport + ' // ' + ss.getSheetByName('INPUT').getRange(j, 1).getValue(); //.toISOString().split('T')[0];
}
}
}
ss.getSheetByName('BILAN').getRange(i, 10).setValue(strDatesTransport);
strDatesTransport = '';
}
};

Try it this way:
function getTransportDates() {
const ss = SpreadsheetApp.getActive();
var sdt = '';
const csh = ss.getSheetByName('BILAN');
const cvs = csh.getRange(4, 1, csh.getLastRow() - 3, csh.getLastColumn()).getValues();
const ish = ss.getSheetByName('INPUT');
const ivs = ish.getRange(4, 1, ish.getLastRow() - 3, ish.getLastColumn()).getValues();
cvs.forEach((cr,i) => {
if((cr[8] > 0)) {
ivs.forEach((ir,j)=>{
if(ir[1] == cr[0]) {
sdt += ir[0];
}
});
}
ss.getSheetByName('BILAN').getRange(i + 4, 10).setValue(sdt);
sdt = '';
});
}
Don't know where this goes: //.toISOString().split('T')[0];

Reduce the number of calls to get info from Google Sheets
Whenever the interpreter comes to something like this:
ss.getSheetByName('INPUT')
... it has to go to the Google Sheet to see if there is (currently) a sheet of that name, and then has to find the relevant cell within that sheet. Even though the script is running on a Google server, accessing a spreadsheet still takes more time than accessing a variable within the local Javascript environment.
The easiest way to reduce the number of calls is to read each of the sheets ("BILAN" and "INPUT") into a local Javascript variable.
In fact, it looks to me like you are extracting extremely specific sets of cells from each of the spreadsheets. Could you get each set of cells into an array, and then process the arrays?

Related

Google App Script- Copy rows to spreadsheet based on value while retaining hyperlink

I am a beginner with JavaScript and have been taking courses for a few months. I have a couple of scripts I have written so far that are up and running. I have an issue that I can’t get past which is very important to find a resolution for, so I’m hoping someone can help.
I have google survey’s that are being submitted and an on even form generator script that creates a form and produces a link in google sheets. The problem I’m having is that I need to be able to filter forms that belong to certain person on to a different spreadsheet using a script. I have written the script a few ways to try to achieve this but have been unsuccessful. The script works as intended but it removes the form generator links when it’s copied or moved.
Here is the filter script I had written:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var trn = ss.getSheetByName('Hull - NY');
var originalData = trn.getRange(2, 1, trn.getLastRow() - 1, 12).getValues();
var centerManager = 'Brooks, Robert';
if (trn.getColumn() == 3 && trn.getValue() == 'Brooks, Robert') {
var r = originalData.getRow();
var data = originalData.filter(function (item) {
return item[1] === centerManager;
});
var targetSheet = ss.insertSheet(centerManager);
targetSheet.getRange(2, 1, data.length, data[0].length).setValues(data).getLastRow();
}
}
I appreciate any help that could be provided!
You must read the formulas and then use the formula instead of the cell value, where a formula exist.
function myFunction() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const trn = ss.getSheetByName('Hull - NY');
const sourceRange = trn.getDataRange()
const originalValues = sourceRange.getValues();
const originalFormulas = sourceRange.getFormulas()
const centerManager = 'Brooks, Robert';
const MANAGER_COL = 2
const data = []
originalValues.forEach((row,i)=>{
if(row[MANAGER_COL-1] === centerManager){
originalFormulas[i].forEach((formula,i)=>{
if(formula[i]){
row[i] = formula
}
})
data.push(row)
}
});
const targetSheet = ss.insertSheet(centerManager);
if(data.length>0){
targetSheet.getRange(2, 1, data.length, data[0].length).setValues(data).getLastRow();
}
}

Automatically generate new divisions and label it

I'm trying to use a script that automatically creates divisions on a spreadsheet. It receives as a value the number of times it has to create the same category of division. Each division/label it's composed of a merge of 6 cells in the same line.
I'm trying to make it work by using getLastRow as a base of the placement of the label, but I can't make it work it out with the merge.
Basically what I'm doing is:
function resumo() {
let ss = SpreadsheetApp.getActive();
let resumo = ss.getSheetByName("Resumo");
let numEntrada = resumo.getRange("c12").getValue();
criaParcela();
function criaParcela() {
for (i = 0;i < numEntrada; i++){
var fLine = resumo.getLastRow();
var bcell = (fLine+1);
var fcell = (fLine+6);
resumo.getRange(fcell,1).setValue("Entrada");
resumo.getRange(bcell,1,6,1).mergeVertically();
}
}
}
As you can notice, I'm not professional programmer.
Assuming that this is the end result you are looking for:
You can try the below script:
function doStuff() {
let spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
let sheetResumo = spreadsheet.getSheetByName("Resumo");
let numEntrada = sheetResumo.getRange("C12").getValue();
for (let i = 0; i < numEntrada; i++) {
let lastRow = sheetResumo.getLastRow() + 1;
sheetResumo.getRange(lastRow, i+1, 6, 1).mergeVertically();
}
}
The most notable changes that have been done to your script are the following:
+ using only one function;
Apps Script does not accept a function within a function in the way you had tried to use it.
+ getRange method is getting the proper parameters in order to perform the merge operation successfully;
As you can see, the i index is needed if you want to merge the same cells numEntrada multiple times.
Reference
Apps Script Sheet Class - getRange(row, column, numRows, numColumns).

Exception: Document is missing (perhaps it was deleted, or you don't have read access?)

I'm working on a project that take "profiles" stored in a Google Sheet, makes a unique Google Doc for each profile, and then updates the unique Google Doc with any new information when you push a button on the Google Sheet.
I have some other automations built into my original code, but I simplified most of it to what's pertinent to the error I'm getting, which is this:
Exception: Document is missing (perhaps it was deleted, or you don't have read access?
It happens on Line 52 of my script in the fileUpdate funtion. Here's the appropriate line for reference:
var file = DocumentApp.openById(fileName);
And this is the rest of my code:
function manageFiles() {
//Basic setup. Defining the range and retrieving the spreadsheet to store as an array.
var date = new Date();
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var array = sheet.getDataRange().getValues();
var arrayL = sheet.getLastRow();
var arrayW = sheet.getLastColumn();
for (var i = 1; i < arrayL; i++) {
if (array[i][arrayW-2] == "") {
//Collect the data from the current sheet.
//Create the document and retrieve some information from it.
var docTitle = array[i , 0]
var doc = DocumentApp.create(docTitle);
var docBody = doc.getBody();
var docLink = doc.getUrl();
//Use a for function to collect the unique data from each cell in the row.
docBody.insertParagraph(0 , "Last Updated: "+date);
for (var j = 2; j <= arrayW; j++) {
var colName = array[0][arrayW-j];
var data = array[i][arrayW-j];
if (colName !== "Filed?") {
docBody.insertParagraph(0 , colName+": "+data);
}
}
//Insert a hyperlink to the file in the cell containing the SID
sheet.getRange(i+1 , 1).setFormula('=HYPERLINK("'+docLink+'", "'+SID+'")');
//Insert a checkbox and check it.
sheet.getRange(i+1 , arrayW-1).insertCheckboxes();
sheet.getRange(i+1 , arrayW-1).setFormula('=TRUE');
}
else if (array[i][arrayW-2] !== "") {
updateFiles(i);
}
}
sheet.getRange(1 , arrayW).setValue('Last Update: '+date);
}
//Note: I hate how cluttered updateFiles is. I'm going to clean it up later.
function fileUpdate(rowNum) {
//now you do the whole thing over again from createFiles()
//Basic setup. Defining the range and retrieving the spreadsheet to store as an array.
var date = new Date();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var array = sheet.getDataRange().getValues();
var arrayL = sheet.getLastRow();
var arrayW = sheet.getLastColumn();
//Collect the data from the current sheet.
var fileName = array[rowNum][0];
var file = DocumentApp.openById(fileName);
//retrieve the body of the document and clear the text, making it blank.
file.getBody().setText("");
//Use a for function to collect the the unique date from every non-blank cell in the row.
file.getBody().insertParagraph(0 , "Last Updated: "+date);
for (var j = 2; j <= arrayW; j++) {
var colName = array[0][arrayW-j];
var data = array[rowNum][arrayW-j];
file.getBody().insertParagraph(0 , colName+": "+data);
}
}
If you'd like to take a look at my sample spreadsheet, you can see it here. I suggest you make a copy though, because you won't have permissions to the Google Docs my script created.
I've looked at some other forums with this same error and tried several of the prescribed solutions (signing out of other Google Accounts, clearing my cookies, completing the URL with a backslash, widening permissions to everyone with the link), but to no avail.
**Note to anyone offended by my janky code or formatting: I'm self-taught, so I do apologize if my work is difficult to read.
The problem (in the updated code attached to your sheet) comes from your URL
Side Note:
In your initial question, you define DocumentApp.openById(fileName);
I assume your realized that this is not correct, since you updated
your code to DocumentApp.openByUrl(docURL);, so I will discuss the
problem of the latter in the following.
The URLs in your sheet are of the form
https://docs.google.com/open?id=1pT5kr7V11TMH0pJea281VhZg_1bOt8YDRrh9thrUV0w
while DocumentApp.openByUrl expects a URL of form
https://docs.google.com/document/d/1pT5kr7V11TMH0pJea281VhZg_1bOt8YDRrh9thrUV0w/
Just adding a / is not enough!
Either create the expected URL manually, or - much easier / use the method DocumentApp.openById(id) instead.
For this, you can extract the id from your URL as following:
var id = docURL.split("https://docs.google.com/open?id=")[1];
var file = DocumentApp.openById(id)

Google app script exceeded execution time limit

I have a Google sheet with almost 160 sub sheets. All the questions are in "All" sheet and B column is the actual spreadsheet name where they should be. The following code is reading data from "All" spreadsheet and sending them perfectly to the last row of desired spreadsheet perfectly but it is taking very long time! Probably because it has a lot of subsheets and is using getSheetByName again and again. Now I've stored all the sub sheets' name and ID in 'sheets' and 'sheetID' arrays at once. I'm thinking to compare between rangeValues[j][1] and sheetNames[k][0]. Below is the code and screenshot of the spreadsheet.
Is this a suitable way? Please help me to run the script faster!
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("All");
var rangeData = sheet.getDataRange();
var lastRow = rangeData.getLastRow();
var searchRange = sheet.getRange(1,1, lastRow, 8);
var curr_sheet;
function send() {
var rangeValues = searchRange.getValues();
var sheetNames = new Array();
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i=0 ; i<sheets.length ; i++) sheetNames.push( [ sheets[i].getName() ] );
//Logger.log (sheetNames.length);
var sheetID = new Array();
var sheetIDs = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i=0 ; i<sheetIDs.length ; i++) sheetID.push( [ sheetIDs[i].getSheetId() ] );
//Logger.log (sheetID.length);
for ( j = 0 ; j < lastRow; j++)
{
for ( k = 0 ; k < sheetNames.length ; k++) //
{
if (rangeValues[j][1] === sheetNames[k][0])
{
var targetSheet = ss.getSheetByName(sheetNames[k][0]); // This line is working but taking very long to complete
var targetSheet = ss.getSheetByID(sheetIDs[k][0]); // This line is not code just to show what I'm thinking to do.
targetSheet.getRange(targetSheet.getLastRow()+1, 1, 1, 8).setValues([rangeValues[j]]);
}
}
}
}
I believe your goal as follows.
You want to reduce the process cost of your script.
Modification points:
There is no method of getSheetByID in Class Spreadsheet. By this, I think that in your case, the following script can be removed.
var sheetID = new Array();
var sheetIDs = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i=0 ; i<sheetIDs.length ; i++) sheetID.push( [ sheetIDs[i].getSheetId() ] );
In your script, getSheetByName and getRange(###).setValues(###) are used in the loop. And also, even when the sheet is the same sheet, each row is put using setValues in the loop every row. In this case, the process cost will be high. I think that these situation might be the reason of your issue.
Fortunately, in your situation, all sheets are in the same Spreadsheet. So for reducing the process cost of your script, here, I would like to propose to put the values to each sheet using Sheets API. The flow of modified script is as follows.
Retrieve Spreadsheet and values.
This script is from your script.
Retrieve all sheets in the Spreadsheet.
Check whether the sheet names from the column "B" are existing.
Create the object for putting to each sheet using Sheets API.
Request the method of spreadsheets.values.batchUpdate of Sheets API using the created object.
When above points are reflected to your script, it becomes as follows.
Modified script:
Please copy and paste the following script. And, please enable Sheets API at Advanced Google services. And then, please run the script.
function send() {
// 1. Retrieve Spreadsheet and values. This script is from your script.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("All");
var rangeData = sheet.getDataRange();
var lastRow = rangeData.getLastRow();
var searchRange = sheet.getRange(1,1, lastRow, 8);
var curr_sheet;
var rangeValues = searchRange.getValues();
// 2. Retrieve all sheets in the Spreadsheet.
var sheets = ss.getSheets().reduce((o, e) => Object.assign(o, {[e.getSheetName()]: e}), {});
// 3. Check whether the sheet names from the column "B" are existing.
// 4. Create the object for putting to each sheet using Sheets API.
var data = rangeValues.reduce((o, e) => {
if (sheets[e[1]]) {
if (o[e[1]]) {
o[e[1]].values.push(e);
} else {
o[e[1]] = {range: `'${e[1]}'!A${sheets[e[1]].getLastRow() + 1}`, values: [e]};
}
}
return o;
}, {});
// 5. Request the method of spreadsheets.values.batchUpdate of Sheets API using the created object.
Sheets.Spreadsheets.Values.batchUpdate({data: Object.values(data), valueInputOption: "USER_ENTERED"}, ss.getId());
}
Note:
In this case, when you run the script, one API call is used. Because the method of spreadsheets.values.batchUpdate is used.
In my environment, when I tested above script and your script using a sample Spreadsheet, I could confirm the reduction of the process cost of about 70 % from your script.
References:
Advanced Google services
Method: spreadsheets.values.batchUpdate
reduce()
Try this:
Should be a lot faster
function send() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName("All");
var rg=sh.getRange(1,1,sh.getLastRow(),8);
var v=rg.getValues();
var names=[];
var sheets=ss.getSheets();
sheets.forEach(function(sh,i){names.push(sh.getName());});
v.forEach(function(r){
let idx=names.indexOf(r[1]);//column2
if(idx!=-1) {
sheets[idx].getRange(sheets[idx].getLastRow()+1,1,1,8).setValues([r]);
}
});
}

Trying to do essentially a vlookup in an AdWords Script but it takes too long

I'm trying to match the location input (which may be a post code or a neighborhood) to the AdWords location ID. I have a massive google sheet with almost 100,000 rows with a list of all the post codes, cities, neighborhoods, etc. and their corresponding IDs, and I'm trying to write a function where you can input post code (for example) and it gives you the location ID. So far I have:
var locationSpread = SpreadsheetApp.openById('scriptID');
var locationSheet = locationSpread.getSheetByName('Sheet1');
var locationRow = locationSheet.getLastRow();
var locationMatch = function(locationInput) {
for (var i = 2; i <= locationRow; i++) {
var locationName = locationSheet.getRange([i], 2).getValue();
if (locationName == locationInput) {
return locationSheet.getRange([i], 1);
}
}
}
Logger.log(locationMatch('DD7'));
I think that in theory this should work, but the limit for AdWords scripts is 30 minutes and since this has to iterate through so many rows it simply isn't feasible, it's taking over 20 minutes to return the example I'm logging, and the script should be doing this dynamically for a bunch of inputs.
Is there a faster/better way of doing this? Or is there an issue with the script itself that's causing it to run so slowly?
Using .getValues() might be faster. You'd need to try it out.
var locationSpread = SpreadsheetApp.openById('asdf');
var locationSheet = locationSpread.getSheetByName('asdf');
var locationRow = locationSheet.getLastRow();
Logger.log("Starting!")
Logger.log("LocationRow: "+locationRow);
function locationMatch(locationInput) {
var locationNames = locationSheet.getRange(1, 2,locationRow,2).getValues();
for (var i = 1; i <= locationRow-1; i++) {
Logger.log("locationNames[i][0]:"+locationNames[i][0])
if (locationNames[i][0] == locationInput) {
return locationSheet.getRange([i], 1);
}
}
}
Logger.log("locationMatch Result:"+locationMatch('MA'));

Categories

Resources