AInternal Error executing custom function - google-apps-script

Is there any limitation in Apps Script?
I'm getting "internal Error" if I set variable "lLen" more then 18 - http://prntscr.com/j60kxb
Actually I need to have this string as var lLen = CIDlist.length; but I'm getting the Error above. In some cases CIDlist.length value can be 160+. When I played to understand the reason of the issue I found that it works if lLen <= 18. Any ideas why it happens?
function myFunction(input) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var sheet = sheets[3];
var lastrow = sheet.getLastRow();
var CIDlist = [];
for(var i =16; i<=lastrow; i++){
var firstval = sheet.getRange("B"+i).getValue();
var secondval = sheet.getRange("C"+i).getValue();
if (firstval == input[0][1] && secondval == input[0][0]) {
var CID = sheet.getRange("A"+i).getValue();
if (CIDlist.indexOf(CID) == -1) {
CIDlist.push(CID);
}
}
}
console.log(input);
console.log(CIDlist.length);
var lLen = 19;
var TotalRevenue = 0;
for (var i=0; i< lLen; i++){
var CIDvalue = CIDlist[i];
for (var j=16; j<=lastrow; j++){
var cid = sheet.getRange("A"+j).getValue();
var revenue = sheet.getRange("D"+j).getValue();
if (cid == CIDvalue) {
TotalRevenue = TotalRevenue + revenue;
}
}
}
return TotalRevenue;
}

The function on the question makes use of two for loops that makes several calls to the SpreadsheetApp classes.
As it fails when is increased a value that control the number of iterations of one of the for loops, which makes that the time execution of the custom function be increased, it's very likely that it's exceeding the 30 second limit for custom functions.

Related

A code meant to delete multiple rows (to improve processing speed) on condition is not working

On my column D there are two values - exp and ok; I am meant to remove all exp rows but there are sometimes 1000's and the macro takes ages to complete.
So from the normal macro - deleting 1 row at a time, I'm trying to delete multimple rows.
function expirate_removed(){
var app = SpreadsheetApp.getActive();
var activesheet = app.getActiveSheet()
var r = activesheet.getRange('D:D');
var v = r.getValues();
for(var i=v.length-1;i>=0;i--)
if( v[0,i-50]=='exp')
activesheet.deleteRows(i-50, 50);
};
I have previously recorded a macro sorting the sheet, making sure all the exp ones are at the bottom, thinking this would help me save a few good minute.
Problem is, whenever I run the macro it deltes one set of 50' rows and then it gives an error saying the rows are out of bounds..
Any suggestions? ( I already have a working version for deleting one row at a time)
Assuming the column D is sorted, try this:
function expirate_removed(){
var app = SpreadsheetApp.getActive();
var activesheet = app.getActiveSheet();
var r = activesheet.getRange('D:D');
var v = r.getValues();
var startRow = 0;
var endRow = 0;
for(var i=0; i<v.length; i++) {
if(v[i] == 'ok'){
startRow = i+1;
break;
}
}
for(var i=startRow; i<v.length; i++) {
if(v[i] != 'ok'){
endRow = i;
break;
}
}
activesheet.deleteRows(startRow, endRow - startRow + 1);
};
Maybe my understanding is wrong but you could use the mapping which helps you to get faster.
function expirate_removed(){
var app = SpreadsheetApp.getActive();
var activesheet = app.getActiveSheet();
var values = activesheet.getRange(1,5,activesheet.getLastRow()).getValues();
var mapValues = values.map(function(r){return r[0]});
mapValues.forEach(function(elt,index){
if(elt == "exp"){
activesheet.deleteRow(index);
}
});

google script maximum execution time - return google group emails (huge list)

Im trying to save google groups emails into a spreadsheet but Im getting the 'maximum execution time' error. Any ideas?
function listGroupMembers() {
var GROUP_EMAIL = "prgg#googlegroups.com";
var group = GroupsApp.getGroupByEmail(GROUP_EMAIL);
var users = group.getUsers();
var sheet = SpreadsheetApp.create("Group Mail");
for (var i = 0; i < users.length; i++) {
sheet.appendRow([users[i]]);
}
}
What is probably taking much of the time is the appendRow() call.
you should build an array with all the values and write this at once in your sheet.
Code could be like this :
function listGroupMembers() {
var GROUP_EMAIL = "prgg#googlegroups.com";
var group = GroupsApp.getGroupByEmail(GROUP_EMAIL);
var users = group.getUsers();
var sheet = SpreadsheetApp.create("Group Mail");
var values = [];
for (var i = 0; i < users.length; i++) {
values.push([users[i]]);
}
sheet.getRange(1,1,values.length, values[0].length).setValues(values);
}
EDIT
I didn't check the begining of your initial code, SpreadsheetApp.create("Group Mail"); returns a spreadsheet, not a sheet... that's why it fails on getRange.
Since it's not clear what you wanted to get exactly I assumed you wanted to create a new Sheet with that name if it doesn't exist.
The appropriate code would be like this :
function listGroupMembers() {
var GROUP_EMAIL = "prgg#googlegroups.com";
var group = GroupsApp.getGroupByEmail(GROUP_EMAIL);
var users = group.getUsers();
if(SpreadsheetApp.getActive().getSheetByName("Group Mail") == null){
var sheet = SpreadsheetApp.getActive().insertSheet("Group Mail");
}else{
var sheet = SpreadsheetApp.getActive().getSheetByName("Group Mail");
}
var values = [];
for (var i = 0; i < users.length; i++) {
values.push([users[i]]);
}
sheet.getRange(1,1,values.length, values[0].length).setValues(values);
}

Get row & column indices of found value

How do I get the row and column indices of the cell containing a value I'm looking for?
Here's an example of two sheets, "Grave" and "Data_grave":
My code, below, should...
First, get a specific value in sheet "Grave" (in example value is - "Win").
Find the number of the row & column with this value in sheet "Data_grave".
Finally, it should write some data ("wow") near the found value "Win" (from column+1).
However, I receive an error message at line 17 (the line following my search loops):
Can't convert 4,4 to (class)
How do I solve that?
function myFind() {
var ss = SpreadsheetApp.getActive(), rowNum = [], collNum = [];
var findData = ss.getSheetByName('Grave').getRange("A2").getValue();
var searchData = ss.getSheetByName('Data_grave').getDataRange().getValues();
for(var i=1, iLen=findData.length; i<iLen; i++) {
for(var j=0, jLen=searchData.length; j<jLen; j++) {
for(var k=0, kLen=searchData[0].length; k<kLen; k++) {
var find = findData;
if(find == searchData[j][k]) {
rowNum.push([j+1]);
collNum.push([k+2]);
}
}
}
}
ss.getSheetByName('Data_grave').getRange(rowNum,collNum).setValue("wow");
}
As Adelin commented: the error message is indicating that you are not using .getRange(rowNum,collNum) properly. That method expects two numbers, but you're providing it two arrays.
When you've "found" the cell you're searching for, instead of push() (which treats rowNum and colNum as arrays), you simply want to use:
var rowNum = j+1;
var colNum = k+2;
You could also use a boolean found as an additional exit condition for all your loops, to stop searching upon success.
function myFind() {
var ss = SpreadsheetApp.getActive(), rowNum = [], collNum = [];
var findData = ss.getSheetByName('Grave').getRange("A2").getValue();
var searchData = ss.getSheetByName('Data_grave').getDataRange().getValues();
var found = false;
for(var i=1, iLen=findData.length; i<iLen && !found; i++) {
for(var j=0, jLen=searchData.length; j<jLen && !found; j++) {
for(var k=0, kLen=searchData[0].length; k<kLen && !found; k++) {
var find = findData;
if(find == searchData[j][k]) {
var rowNum = j+1;
var collNum = k+2;
found = true;
}
}
}
}
ss.getSheetByName('Data_grave').getRange(rowNum,collNum).setValue("wow");
}

Handling time duration in Google Sheets Api

I'm making a basic script that fetches time durations from external sheets, and sums them. What I have so far is:
function getHorasCasoUso() {
var libros = {
"key1" : "externalSheetURL1",
"key2" : "externalSheetURL2",
...
};
var horas_por_caso_uso = {};
for (var key in libros) {
var libro = SpreadsheetApp.openByUrl(libros[key]);
var sheets = libro.getSheets();
for (var i = 0; i < sheets.length; i++) {
var sheet = sheets[i];
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
for (var j = 5; j < numRows; j++) {
var row = values[j];
var caso_uso = row[6];
var horas = row[4]; //The cell format is 'Duration'
if (!caso_uso)
continue;
if (!!horas_por_caso_uso[caso_uso])
horas_por_caso_uso[caso_uso] += horas;
else
horas_por_caso_uso[caso_uso] = horas;
}
}
}
var ss = SpreadsheetApp.getActiveSheet();
for (var key in horas_por_caso_uso) {
ss.appendRow([key, horas_por_caso_uso[key]]);
}
}
The problem is that the data stored in 'horas' is a string. I want to get the time duration in that cell. How can I do that?
Your issue seems quite similar to the one in this post but at a larger scale...
You should convert row[4] value to minutes (or seconds if you need this accuracy) before adding that value to the total counter.
If the cells are formatted as duration (as you say it is) it should work without changes.(see code at the end of this post)
If not, ie if these values are returned as strings then a simple string manipulation will do the job for you like this :
example : testString = 12:34
function toMinutes(value){
var minutes = Number(value.split(':')[0].replace(' ',''))*60+Number(value.split(':')[1].replace(' ',''));
return minutes;
}
(code working as a function) will return 754 (60*12+34)
usage in your code : var horas = toMinutes(row[4]);
function toMinutes(value){
var duration = new Date(value);
var min = duration.getMinutes()+60*duration.getHours();
return min;
}
You can eventually improve this function with a few error trapping features to handle cases where cell is empty of malformed... Since I don't know what the data look like I prefer let you do it yourself.

Google Apps Script: Function to update names from one sheet to another times out. Please refine my loops

I have an app through google docs that I use to record student attendance to my after school tutoring center. I have had higher than usual attendance and my code is not efficient enough to run before timing out. Please assist me in editing my code so that it runs more effectively.
I have to note that I am an absolute beginner, so if this is inappropriately posted, please give me constructive feedback on how to get help with this problem. And I apologize in advance for the (I assume) horrible code you are about to see - it's the best I could do and I worked very hard to create this.
Thank you.
Code:
function updateAttendance(){
var itemSpreadsheetKey = '';
var openedSS = SpreadsheetApp.openById(itemSpreadsheetKey);
var sheetStudentNames = openedSS.getSheetByName("StudentNames");
var sheetDailyData = openedSS.getSheetByName("DailyData");
var app = UiApp.getActiveApplication();
var ss = SpreadsheetApp.openById(itemSpreadsheetKey);
var dailyDataSheet = ss.getSheetByName("DailyData");
var studentNameSheet = ss.getSheetByName("StudentNames");
var dailyData = dailyDataSheet.getDataRange();
var studentNames = studentNameSheet.getDataRange();
var dailyLastRow = dailyData.getLastRow();
var studentNamesLastRow = studentNames.getLastRow();
var studentNamesLastColumn = studentNames.getLastColumn();
var dailyDataNamesArray = sheetDailyData.getRange(2, 1, dailyLastRow).getValues();
for (var i=1; i<=dailyDataNamesArray.length; i++) {
if (i != ""){
var dailyTime = dailyData.getCell(i, 5).getValue();
for (var j=2; j<=studentNamesLastRow; j++) {
var today = dailyData.getCell(1, 8).getValue();
if (dailyData.getCell(i, 1).getValue() == studentNames.getCell(j, 1).getValue()) {
studentNames.getCell(1, studentNamesLastColumn).offset(0, 1).setValue(today);
studentNames.getCell(j, studentNamesLastColumn).offset(0, 1).setValue(dailyTime);
}
}
}
}
return app;
}
If you want your script to go faster you must use array manipulation instead of all these range.setValue() .
Change all the values in a global array (a pair of actually) and when you're done just write these arrays to their respective sheets.
The difference will be enormous ! believe me ;)
All this is pretty well explained in the documentation about best practices.
Here is a "translation" of your code, I'm not sure I didn't make any error in transposition but I have no way to check without knowing what is in your sheets.
Just remember that arrays are 0 indexed while ranges start at 1.
function updateAttendance(){
var itemSpreadsheetKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx';
var openedSS = SpreadsheetApp.openById(itemSpreadsheetKey);
var sheetStudentNames = openedSS.getSheetByName("StudentNames");
var sheetDailyData = openedSS.getSheetByName("DailyData");
var app = UiApp.getActiveApplication();
var ss = SpreadsheetApp.openById(itemSpreadsheetKey);
var dailyDataSheet = ss.getSheetByName("DailyData");
var studentNameSheet = ss.getSheetByName("StudentNames");
var dailyData = dailyDataSheet.getDataRange();
var studentNames = studentNameSheet.getDataRange().getValues();
var dailyLastRow = dailyData.getLastRow();
var studentNamesLastRow = studentNames.getLastRow();
var studentNamesLastColumn = studentNames.getLastColumn();
var dailyDataNamesArray = sheetDailyData.getDataRange().getValues();
for (var i = 1 ; i < dailyDataNamesArray.length; i++) {
if (i != ""){
var dailyTime = dailyDataNamesArray[i][4];
for (var j=1; j<studentNamesLastRow; j++) {
var today = dailyDataNamesArray[0][7];
if (dailyDataNamesArray[i][0] == studentNames[i][0]) {
studentNames[0][studentNamesLastColumn+1] = today;
studentNames[j][studentNamesLastColumn+1] = dailyTime;
}
}
}
}
studentNameSheet.getRange(1,1,studentNames.length,studentNames[0].length).setValues(studentNames):
dailyDataSheet.getRange(1,1,dailyDataNamesArray.length,dailyDataNamesArray[0].length).setValues(dailyDataNamesArray):
return app;
}
Replace the code after the var dailyDataNamesArray line with the following. It's a bit of a guess, so I hope for you that it works:
var todayCell1 = studentNames.getCell(1, studentNamesLastColumn).offset(0, 1);
for (var i=1; i<=dailyDataNamesArray.length; i++) {
if (i != ""){
var dailyTime = dailyData.getCell(i, 5).getValue();
var dailyVar2 = dailyData.getCell(i, 1).getValue();
for (var j=2; j<=studentNamesLastRow; j++) {
var today = dailyData.getCell(1, 8).getValue();
if ( dailyVar2 == studentNames.getCell(j, 1).getValue()) {
todayCell1.setValue(today);
studentNames.getCell(j, studentNamesLastColumn).offset(0, 1).setValue(dailyTime);
}
}
}
}