Optimizing Script run time gathering large volume of spreadsheet tab information - google-apps-script

I'm hoping that someone might have an alternative suggestion for a strategy for the Google Apps Script I wrote below to help it finish running before it times out. I have a lengthy list of Spreadsheets (about 200) that each have anywhere from 20-100 individual sheets/tabs in them. I'm trying to loop through the entire list of spreadsheets to gather information about each individual sheet (about 8,000 in total) but unfortunately it's running to slow to complete in one run.
Thanks in advance for any suggestions
function getListofTabs(urlName) {
var ssRecipeTable = SpreadsheetApp.openByUrl(urlName);
var tabs = ssRecipeTable.getSheets();
for(var i=0;i<tabs.length;i++){
sheetName = tabs[i].getName();
sheetID = tabs[i].getSheetId();
sheetURL = tabs[i].getParent().getId();
//Logger.log(sheetName,sheetID,sheetURL);
dataList.appendRow(['=HYPERLINK("https://docs.google.com/spreadsheets/d/'+tabs[i].getParent().getId()+'/edit#gid='+tabs[i].getSheetId()+'","'+tabs[i].getName()+'")',tabs[i].getName(),tabs[i].getParent().getId(),tabs[i].getSheetId()]);
}
}
//Function to feed getListofTabs function with list of all recipe workbooks
function getArray(){
//Temp location for active tests
var recipeDataSS = SpreadsheetApp.getActiveSpreadsheet();
//List of all Recipe workbooks from master Tested Recipe folder
var listofWorkbooks = recipeDataSS.getSheetByName('ListofWorkbooks');
//Location for data input sheet for Plx Upload
var dataList = recipeDataSS.getSheetByName('Spreadsheets2');
//gets list of workbooks urls, loops through until the end
var dataRecipe = listofWorkbooks.getDataRange().getValues();
for(var x=0; x<dataRecipe.length; x++)
{
var urlName = dataRecipe[x][0];
getListofTabs(urlName);
}}```

You should get rid of appendRow() in the for loop.
In order to do that you need to create an empty array before the for loop and then push() the new values into this array. Then after the for loop, use setValues() to add the values to the sheet. In this way, you call setValues() once instead of calling appendRow() tabs.length number of times.
For example the function getListofTabs(urlName) could be replaced by that:
function getListofTabs(urlName) {
var ssRecipeTable = SpreadsheetApp.openByUrl(urlName);
var dat = []
var tabs = ssRecipeTable.getSheets();
for(var i=0;i<tabs.length;i++){
sheetName = tabs[i].getName();
sheetID = tabs[i].getSheetId();
sheetURL = tabs[i].getParent().getId();
//Logger.log(sheetName,sheetID,sheetURL);
dat.push(['=HYPERLINK("https://docs.google.com/spreadsheets/d/'+tabs[i].getParent().getId()+'/edit#gid='+tabs[i].getSheetId()+'","'+tabs[i].getName()+'")',tabs[i].getName(),tabs[i].getParent().getId(),tabs[i].getSheetId()]);
}
dataList.getRange(dataList.getLastRow()+1,1,dat.length,dat[0].length).setValues(dat);
}
Please read carefully the best practices when writing GAS.

Related

Google sheet + Apps script: faster then for loop

Hi I'm using this code to loop trough another google worksheet find in column 'B' the id number 'idNum' once the id number is found the script replaces the whole row with new data but it seem to be taking a long time before the following script get triggered is there a way to make it faster to loop though or to make the next script trigger faster.
here's my code Thanks
function editRow(){
var mainsheet = ('10_XEaQiR71----- Sheet ID ----uOhi9VVtk5FI')
var tsheet = SpreadsheetApp.openById(mainsheet).getSheetByName('Data')
var targetSheet1 = tsheet.getDataRange().getValues()
var sheet = SpreadsheetApp.getActive();
var sourceData = sheet.getSheetByName('Data Input').getRange('A2:IB2').getValues()[0];
var idNum = sheet.getSheetByName('Data Input').getRange ("B2").getValue();
var copyFrom = sheet.getSheetByName('Data Input').getRange('A2:IB2')
var data = copyFrom.getValues()
for(var i = 0; i<targetSheet1.length;i++){
if(targetSheet1[i][1] == idNum){
var row = i=i+1
tsheet.getRange('A'+row+':IB'+row).setValues(data);
break;
}
}
next script
}
I believe your goal as follows.
You want to reduce the process cost of your script in your question.
Issue and workaround:
In your script, the values are retrieved from getRange("B2") and getRange('A2:IB2'). In this case, I think that you can retrieve them from getRange('A2:IB2').
When you are using V8 runtime, the process cost of for loop is almost the same with others. Ref So in this case, I would like to propose to use TextFinder instead of the for loop. Because I thought that TextFinder is run in the internal server and by this, the search process might be able to be reduced a little. But I'm not sure about your actual situation. So I'm not sure whether this is the correct direction for achieving your issue. So, please test the following script.
When your script is modified, it becomes as follows.
Modified script:
function editRow() {
var mainsheet = '10_XEaQiR71----- Sheet ID ----uOhi9VVtk5FI';
var sheet = SpreadsheetApp.getActive();
var data = sheet.getSheetByName('Data Input').getRange('A2:IB2').getValues();
var idNum = data[0][1];
var tsheet = SpreadsheetApp.openById(mainsheet).getSheetByName('Data');
var row = tsheet.getRange("B1:B" + tsheet.getLastRow()).createTextFinder(idNum).findNext().getRow();
tsheet.getRange(row, 1, 1, data[0].length).setValues(data);
// next script
}
Reference:
Class TextFinder

Fetch data from source sheet based on emails provided in the Emails tab in the target spreadsheet

I have a problem where I have two sheets. one sheet is the source spreadsheet and another is a target spreadsheet. The source spreadsheet has a source sheet has which is the master database and the target spreadsheet has the target where we want to fetch data from source sheet based on emails provided in the Emails tab in the target spreadsheet.
I want the following things to happen with a script and not with IMPORTRANGE or QUERY:
The target spreadsheet will have multiple copies so I want to connect the target spreadsheet with the source spreadsheet based on the source spreadsheet's id.
I want the email matches to be case insensitive so that the users of the target spreadsheet can type emails in any case.
The Emails can go up to 50 or let's say get the last row for that column.
It will be great if the script shows a pop up saying updated after it has fetched the data.
The source sheet might have data up to 15000 rows so I am thinking about speed too.
I have shared both of the spreadsheets with hyperlinks to their names. I am not really great at scripts so it will be helpful if you can leave comments in it wherever you feel like. I would truly appreciate your help.
Thanks in advance!
Script here:
function fetch() {
//get the sheets
var source_Ssheet = SpreadsheetApp.openById('19FkL3rsh5sxdujb6x00BUPvXEEhiXfAeURTeQi3YWzo');
var target_Ssheet = SpreadsheetApp.getActiveSpreadsheet();
//get the tabs
var email_sheet = target_Ssheet.getSheetByName("Emails");
var target_sheet = target_Ssheet.getSheetByName("Target Sheet");
var source_sheet = source_Ssheet.getSheetByName("Source Sheet");
//get ranges
var email_list = email_sheet.getRange("B2:B");
var target_sheet_range = target_sheet.getRange("A1:F100");
var source_sheet_range = source_sheet.getRange("A1:F100");
//get last rows
var last_email_name = email_list.getLastRow();
var last_target_sheet_range = target_sheet_range.getLastRow();
var last_source_sheet_range = source_sheet_range.getLastRow();
//start searching for emails
for (var i=3; i < last_email_name.length+1; i++)
{
for(varj=3; j< last_source_sheet_range.length+1; j++ )
{
if(source_sheet_range[j][3].getValue() == email_list[i][3].getValue())
{
//copy matches to target sheet
target_sheet.getRange((last_target_sheet_range + 1),1,1,10).setValues(master_sheet_range[j].getValues());
}
}
}
}
Several things
last_email_name and last_source_sheet_range are numbers - they do not have any length, this is why your first forloops are not working
You are missing a space in varj=3;
email_list[i][3].getValue() does not exist because email_list only includes B - that only one column. I assume you meant email_list[i][0].getValue()
ranges cannot be addressed with the indices [][], you need to retrieve the values first to have a 2D value range.
You email values in the different sheets do not follow the same case. Apps Script is case sensitive, to suee the == comparison you need to use the toLowerCase() method.
Also mind that defining getRange("B2:B") will include many empty rows that you don't need and will make your code very slow. Replace it through getRange("B2:B" + email_sheet.getLastRow());
Have a look here at the debugged code - keep in mind that there is still much room for improvement.
function fetch() {
//get the sheets
var source_Ssheet = SpreadsheetApp.openById('19FkL3rsh5sxdujb6x00BUPvXEEhiXfAeURTeQi3YWzo');
var target_Ssheet = SpreadsheetApp.getActiveSpreadsheet();
//get the tabs
var email_sheet = target_Ssheet.getSheetByName("Emails");
var target_sheet = target_Ssheet.getSheetByName("Target Sheet");
var source_sheet = source_Ssheet.getSheetByName("Source Sheet");
//get ranges
var email_list = email_sheet.getRange("B2:B" + email_sheet.getLastRow()).getValues();
var target_sheet_range = target_sheet.getRange("A1:F100").getValues();
var source_sheet_range = source_sheet.getRange("A1:F100").getValues();
var last_target_sheet_range = target_sheet.getLastRow();
//start searching for emails
for (var i=1; i < email_list.length; i++)
{
for(var j=1; j< source_sheet_range.length; j++ )
{
if(source_sheet_range[j][0].toLowerCase() == email_list[i][0].toLowerCase())
{
target_sheet.getRange((last_target_sheet_range + 1),1,1,6).setValues([source_sheet_range[j]]);
}
}
}
}

Extract data from gmail & pass to Google sheet

I got code for extracting data from Gmail and pass it to google sheet.
Please find the code.
The problem for me is, the entire data is being copied to the first row alone.
Gmail message:
UserName ID
XXX 23
YYY 45
What I got in Google sheet is:
entire 3*4 table format content in first row(A1) itself.
I need to append this information in separate cells instead of single cell.
You are not specifying which cells you want it to go in and you therefore keep appending into cell A1 (extremely and terribly inefficient) the code below is specifying cell A1 down.
function addToDo() {
var label = GmailApp.getUserLabelByName("Samples");
var threads = label.getThreads();
var sheet = SpreadsheetApp.getActiveSheet();
var body = [];
for(var i=0;i<threads.length; i++){
body[body.length] = [threads[i].getMessages()[0].getPlainBody()]; //add to array in google app scripts
}
sheet.getRange(1,1,body.length,1).setValues(body); //dump array all at once to google sheets
}
Please read this resource as it will help you from making more terrible mistakes in your GAS (google app script) code as relating to google sheets. The first bad example is exactly what you were trying to do.
https://developers.google.com/apps-script/guides/support/best-practices
Your code has the issue where it inserts a single object as a cell and it does it in an inefficient way.
You should get all the data and set it at once. This allows Google to perform bulk operations that are more efficient.
Also, if you want to insert the data in multiple columns, you need to separate it and place each columns as an object (in my example, into data).
function myFunction() {
var label = GmailApp.getUserLabels()[0];
var threads = label.getThreads();
var sheet = SpreadsheetApp.getActiveSheet();
var data = [];
for (var i=0; i<threads.length; i++) {
var threadId = threads[i].getId();
var firstmessage = threads[i].getMessages()[0];
var subject = firstmessage.getSubject();
var body = firstmessage.getPlainBody();
data.push([threadId, subject, body]); //Change this to have the columns you want
}
sheet.getRange(sheet.getDataRange().getLastRow(), 1, data.length, data[0].length).setValues(data);
}

Script to copy data between sheets

I'm trying to create a script to copy data from sheet 1 to sheet 2 and at the same time reorder it. I get my data from a Google form, so data is constantly updating.
Here are two images as examples. N°1 is how I have my data, N°2 is how I want it to be in sheet 2.
The idea is to have the script copying the data every time a new row appears.
Data from Forms.
This is how I would like it to be.
This is my initial code:
function copyrange() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Ingreso'); //source sheet
var testrange = sheet.getRange('J:J');
var testvalue = (testrange.getValues());
var csh = ss.getSheetByName('Auxiliar Ingreso'); //destination sheet
var data = [];
var columnasfijas = [];
var cadena = [];
//Condition check in G:G; If true copy the same row to data array
for (i=1; i<testvalue.length;i++) {
data.push.apply(data,sheet.getRange(i+1,1,1,9).getValues());
if ( testvalue[i] == 'Si') {
data = (sheet.getRange(i+1,1,1,9).getValues()).concat (sheet.getRange(i+1,11,1,9).getValues()); // this beaks up into 2 rows Idon't know why
/*cadena = (columnasfijas);
data.push.apply(data, columnasfijas);*/
}
csh.getRange(csh.getLastRow()+1,1,data.length,data[0].length).setValues(data);
}
//Copy data array to destination sheet
//csh.getRange(csh.getLastRow()+1,1,data.length,data[0].length).setValues(data);
}
In this line, I'm also having trouble concatenating different lengths of data. It should be: (i+1,1,1,6). concat.....(i+1,11,1,3)
data = (sheet.getRange(i+1,1,1,9).getValues()).concat (sheet.getRange(i+1,11,1,9).getValues()); // this beaks up into 2 rows Idon't know why
When I run it as it should by I receive an error that the length should be 9 instead of 3.
This can be accomplished more simply using formulas instead of app scripts:
=sort(importrange("spreadsheetURL", "Sheet1!A2:AA10000"),sort_col#,TRUE/FALSE,[sort_col2#],[TRUE/FALSE]...)
Documentation on importrange function: https://support.google.com/docs/answer/3093340
Documentation on sort function: https://support.google.com/docs/answer/3093150
Once you input the formula, there will likely red triangle on the cell, be sure to click on the cell and click the Allow Access button to give one spreadsheet access to the other.

Google App Scripts - Copy file into folder with names from ranges

I'm working in Google Sheets and I'm trying to create a script that will make a set number of copies of the current file, giving each copy the next name from a list of names in a range, and place those files in folders that were created by a previous script. I was able to get it all working, but only for the first file (out of 6, and possible far more) and can't figure out what I'm doing wrong.
Here's a copy of the sheet. I also have another version of this that works to just create copies of the document, but I'm trying to streamline the process for my end users who may be creating dozens of copies and was hoping that doing the organization for them would help.
Thanks for your help!
Here's the script:
function createcopies2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Get the range of cells that store necessary data.
var CoachNames = ss.getRangeByName("CoachNames");
var CoachObjects = CoachNames.getValues();
var schoolNames = ss.getRangeByName("SchoolNames");
var SchoolObjects = schoolNames.getValues();
var id = ss.getId();
// The next variable gets a value that counts how many CoachNames there are in the spreadsheet
var coaches = ss.getRangeByName("Coaches");
var numcoaches = coaches.getValue();
//here's the function
for(i=0; i<numcoaches; i++){
var drive=DriveApp.getFileById(id);
var name=CoachObjects[i].toString();
var folder=DriveApp.getFoldersByName(SchoolObjects[i]).next();
var folderid=folder.getId();
var folder2=DriveApp.getFolderById(folderid)
if(folder) {
drive.makeCopy(name, folder2)}
else{
drive.makeCopy(name);
}
return;
}
}
You are on the right track.
I have modified you below, with explanation:
function createcopies2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Get the range of cells that store necessary data.
var CoachNames = ss.getRangeByName("CoachNames");
//The below statements a 2D dimensional array.
//To access the individual value you will have a statement like this
//CoachObjects[0][0],CoachObject[1][0],[2][0] ..., down the row
var CoachObjects = CoachNames.getValues();
var schoolNames = ss.getRangeByName("SchoolNames");
//The below statements a 2D dimensional array.
var SchoolObjects = schoolNames.getValues();
var id = ss.getId();
// The next variable gets a value that counts how many CoachNames there are in the spreadsheet
var coaches = ss.getRangeByName("Coaches");
var numcoaches = coaches.getValue();
//Moved the below statement out of the loop
// Baceause you are using the same file
var drive=DriveApp.getFileById(id);
//here's the function
for(i=0; i<numcoaches; i++){
var name=CoachObjects[i][0].toString();
var folder=DriveApp.getFoldersByName(SchoolObjects[i][0]).next();
var folderid=folder.getId();
var folder2=DriveApp.getFolderById(folderid)
if(folder) {
drive.makeCopy(name, folder2)}
else{
drive.makeCopy(name);
}
return;
}
}
I modified the code since you get a 2D array from getValues
//The below statements a 2D dimensional array.
var CoachObjects = CoachNames.getValues();
To access the individual value you will use a statement like this
`CoachObjects[0][0]`
CoachObjects[1][0]
....... [2][0] ...
down the row
Also, These are redundant lines of code:
var folder=DriveApp.getFoldersByName(SchoolObjects[i][0]).next();
var folderid=folder.getId();
var folder2=DriveApp.getFolderById(folderid)
you can just replace it with
var folder2=DriveApp.getFoldersByName(SchoolObjects[i][0]).next();