Google Script - Internal Error after 15 seconds - google-apps-script

So I am writing a script that we give the sum of all the data that has a specific tag in the same row.
Col 1 | Col 2
-------+---------
grp1 | 2
grp1 | 1
grp2 | 1
-------+---------
If I was to pass this function grp1 the result would be 3.
When I use this script over 1000 rows, I get an error "Internal Error Executing the custom function" after a short time (like 15 seconds). I thought it might be the timeout but it happens well before 30 seconds. Any ideas?
function collectgrpdata(group, startrow) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lastrow = sheet.getLastRow();
var currentcell = sheet.getActiveCell();
var col = currentcell.getColumn();
var total = 0;
for(var x = startrow; x <= lastrow; x++) {
var v = sheet.getRange(x, col).getValue();
if(v != "" ) {
if (sheet.getRange(x, 2).getValue() == group) {
total += v;
}
}
}
return total
}

Your problem is most likely due to the fact that you do so many calls to getRange and getValue.
You are probably hitting your quota limit of calls.
instead of doing that do one big call to get all the data and then work with that:
function collectgrpdata2(group, startrow) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var currentcell = sheet.getActiveCell();
var col = currentcell.getColumn();
var range = sheet
.getRange(startrow,
col,
sheet.getLastRow() - startrow + 1,
sheet.getLastColumn() - col + 1)
var data = range.getValues();
return data
.filter(function(row) {return row[0] === group;})
.map(function(row) {return row[1];})
.concat(0)
.reduce(function(x,y) {return x+y;});
}

Related

How to sorting within same date data based on time in google sheet using Apps Script?

I want to sort data based on date(Column A) and start time(Column I). If date column have more than 1 same date then again shorting by start time within this same date contain rows.
i have a code already that sorting by date.
function autoSort() {
var headerRows = 1;
var sortFirst = 1; // 1 is Column "A"
var sortFirstAsc = false; // When it's "true", the order is ascending.
var sortSecond = 9; // 3 is Column "C"
var sortSecondAsc = false; // When it's "true", the order is ['OPEN','YES','NO'].
// Retrieve values from Spreadsheet.
var activeSheet = SpreadsheetApp.getActiveSheet();
var sheetName = activeSheet.getSheetName(); //name of sheet to be sorted
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var range = sheet.getRange(headerRows+1, 1, sheet.getLastRow()-headerRows, sheet.getLastColumn());
var values = range.getValues();
// Sort the date of column "A".
var s1 = sortFirstAsc ? 1 : -1;
values.sort(function(a, b) {return (a[sortFirst - 1] < b[sortFirst - 1] ? -s1 : s1)});
// Sort the values of column "C" with the custom sort using the keys.
var sortOrder = ['OPEN','YES','NO'];
var s2 = sortSecondAsc ? 1 : -1;
values.sort(function(a, b) {
var i1 = sortOrder.indexOf(a[sortSecond - 1]);
var i2 = sortOrder.indexOf(b[sortSecond - 1]);
var vlen = values.length;
return s2 * ((i1 > -1 ? i1 : vlen) - (i2 > -1 ? i2 : vlen));
});
sheet.getRange(2, 1, values.length, values[0].length).setValues(values);
}

How to increase speed of my script containing loops in Google Sheets?

I have an issue with a slow speed of my Google Apps Script containing a loop. I'm looking for recommendations how to increase the speed.
I have the following input data on a sheet named "Test" (just an extract for demonstration purposes but have in mind that the original data is much larger - with much more products and suppliers):
I want to achieve on a separate sheet named "Suppliers" the following result:
The idea is the suppliers with their respective products and prices to be reorganized in the format shown above. The conditions are:
Exclude inactive products;
Exclude inactive suppliers;
include only products per supplier with an available price;
Dynamic update of the data when adding new products/ suppliers, or changing anything on the input table.
I have done the following script - it's working, but speed is quite slow considering the fact that original data is large. I would highly appreciate any advises how to optimize it. I spent much time trying to optimize it by myself but to no avail. Many thanks in advance!
function Suppliers() {
var file = SpreadsheetApp.getActiveSpreadsheet();
var sheetSourceName = 'Test';
var sheetDestinationName = 'Suppliers';
var sourceSheet = file.getSheetByName(sheetSourceName);
var numRows = sourceSheet.getLastRow();
var numCols = sourceSheet.getLastColumn();
var destinationSheet = file.getSheetByName(sheetDestinationName);
var sheet = file.getActiveSheet();
if( sheet.getSheetName() == sheetSourceName ) {
var lastRow = destinationSheet.getLastRow();
destinationSheet.getRange( 2, 1, lastRow, 3 ).clear( { contentsOnly : true } );
var lastRow = 1;
for( var s = 3; s < numCols + 3; s++ ){
for( var p = 3; p < numRows + 3; p++ ){
var product = sourceSheet.getRange( p, 2 ).getValues();
var activeProduct = sourceSheet.getRange( p, 1 ).getValues();
var price = sourceSheet.getRange( p, s ).getValues();
var supplier = sourceSheet.getRange( 2, s ).getValues();
var activeSupplier = sourceSheet.getRange( 1, s ).getValues();
if( activeProduct == "active" && price > 0 && activeSupplier == "active" ){
lastRow = lastRow + 1;
destinationSheet.getRange( lastRow, 1 ).setValues( product );
destinationSheet.getRange( lastRow, 2 ).setValues( price );
destinationSheet.getRange( lastRow, 3 ).setValues( supplier );
}
}
}
}
}
No guarantee because I don't have a data sheet set up to match your situation but I think this will work for you. Try it in a copy of your spreadsheet. Note that in your getRange you use column and row which start with 1. I'm loading everything into an array a looping through its values where the index starts with 0. So it becomes your row/col -1.
function Suppliers() {
var file = SpreadsheetApp.getActiveSpreadsheet();
var sheetSourceName = 'Test';
var sheetDestinationName = 'Suppliers';
var sourceSheet = file.getSheetByName(sheetSourceName);
var numRows = sourceSheet.getLastRow();
var numCols = sourceSheet.getLastColumn();
var destinationSheet = file.getSheetByName(sheetDestinationName);
var sheet = file.getActiveSheet();
if( sheet.getSheetName() == sheetSourceName ) {
destinationSheet.getRange( 2, 1, lastRow, 3 ).clear( { contentsOnly : true } );
var sourceData = sourceSheet.getRange(1,1,sourceSheet.getLastRow(),sourceSheet.getLastColumn()).getValues();
var destinationData = [];
// Not sure why +3 added to numRows and numCols because there is no data there
for( var s=2; s<numCols; s++ ) {
for( var p=2; p<numRows; p++ ) {
var product = sourceData[p][1];
var activeProduct = sourceData[p][0];
var price = sourceData[p][s];
var supplier = sourceData[1][s];
var activeSupplier = sourceData[0][s];
if( activeProduct == "active" && price > 0 && activeSupplier == "active" ){
destinationData.push([product,price,supplier]);
}
}
}
destinationSheet.getRange(2,1,destinationData.length,3).setValues(destinationData);
}
}

Delete row values in more than 1 sheet if exists in another sheet

The code below is from an answer from this post regarding copying row values to a new sheet if it exist in another sheets.
Now, what if instead of copying the duplicate values to sheet 3, I want to delete them from sheets 1 and 2 if it exists in Sheet 3. With the same spreadsheet, I have 3 sheets. The unique value that will be compared on the first 2 sheets is the first column, "ID NUMBER".
Given the values, 784 | John Steep | I.T Department, which exists in all 3 sheets, the same row value should be deleted in Sheet 1 and 2 and retain the same value on Sheet 3.
function copyRowtoSheet3() {
var s1 = SpreadsheetApp.openById("ID").getSheetByName('Sheet1');
var s2 = SpreadsheetApp.openById("ID").getSheetByName('Sheet2');
var s3 = SpreadsheetApp.openById("ID").getSheetByName('Sheet3');
var values1 = s1.getDataRange().getValues();
var values2 = s2.getDataRange().getValues();
var resultArray = [];
for(var n=0; n < values1.length ; n++){
var keep = false;
for(var p=0; p < values2.length ; p++){
Logger.log(values1[n][0]+' =? '+values2[p][0]);
if( values1[n][0] == values2[p][0] && values1[n][3] == values2[p][4]){
resultArray.push(values1[n]);
Logger.log('true');
break ;// remove this if values are not unique and you want to keep all occurrences...
}
}
}
s3.getRange(+1,1,resultArray.length,resultArray[0].length).setValues(resultArray);
}
Can't seem to find the right solution. Tried several scripts but failed to make it work.
Thank you for any advice/suggestion.
Although the other answer works (I didn't test but I guess it does) it uses a lot of spreadsheetApp calls and might be slow if you have a lot of data.
It is possible to get this result using only arrays (if you don't need to keep sheet formatting and/or formulas).
The approach is slightly different as it is easier to keep data instead of removing it.
There are for sure many possible solutions, below is the one I tried : I created a special array that contains only the first column of sheet3 to make the duplicate search simpler.
function removeDupsInOtherSheets() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s1 = ss.getSheetByName("Sheet1").getDataRange().getValues();
var s2 = ss.getSheetByName("Sheet2").getDataRange().getValues();
var s3 = ss.getSheetByName("Sheet3").getDataRange().getValues();
// iterate s3 and check in s1 & s2 if duplicate values exist
var nS1 = [];
var nS2 = [];
var s3Col1 = [];// data in column1 of sheet3
for(var n in s3){
s3Col1.push(s3[n][0]);
}
for(var n in s1){ // iterate sheet1 and test col 1 vs col 1 in sheet3
var noDup1 = checkForDup(s1[n],s3Col1)
if(noDup1){nS1.push(noDup1)};// if not present in sheet3 then keep
}
for(var n in s2){ // iterate sheet2 and test col 1 vs col 1 in sheet3
var noDup2 = checkForDup(s2[n],s3Col1)
if(noDup2){nS2.push(noDup2)};// if not present in sheet3 then keep
}
Logger.log(nS1);// view result
Logger.log(nS2);
ss.getSheetByName("Sheet1").getDataRange().clear();// clear and update sheets
ss.getSheetByName("Sheet2").getDataRange().clear();
ss.getSheetByName("Sheet1").getRange(1,1,nS1.length,nS1[0].length).setValues(nS1);
ss.getSheetByName("Sheet2").getRange(1,1,nS2.length,nS2[0].length).setValues(nS2);
}
function checkForDup(item,s){
Logger.log(s+' = '+item[0]+' ?')
if(s.indexOf(item[0])>-1){
return null;
}
return item;
}
Sheet1
ID NUMBER NAME DEPARTMENT
784 John Steep I.T.
901 Liz Green H.R.
Sheet2
ID NUMBER NAME DEPARTMENT
784 John Steep I.T.
653 Bo Gore Marketing
Sheet3
ID NUMBER NAME DEPARTMENT
784 John Steep I.T.
999 Frank White Sales
121 Abid Jones Engineering
901 Liz Green H.R.
Script
function main() {
var ss = SpreadsheetApp.openById("ID");
var s1 = ss.getSheetByName("Sheet1");
var s2 = ss.getSheetByName("Sheet2");
var s3 = ss.getSheetByName("Sheet3");
var idCol = 1; // Assuming location of ID column is same in all sheets.
var s1RowCount = s1.getLastRow();
for (var i = 2; i <= s1RowCount; i++) { // Start at var i = 2 to skip the
// first row containing the header.
var id = s1.getRange(i, idCol, 1, 1).getValue();
deleteDuplicates(s2, id);
deleteDuplicates(s3, id);
}
}
function deleteDuplicates(sheet, id) {
var idCol = 1; // Assuming location of ID column is same in all sheets.
var rowCount = sheet.getLastRow();
for (var i = 2; i <= rowCount; i++) {
var data = sheet.getRange(i, idCol, 1, 1).getValue();
if (data === id) {
// Use this to test out the function.
Logger.log("Duplicate of ID " + id + " in sheet " +
sheet.getSheetName() + " at row " + i);
// Uncomment the next line when ready.
// sheet.deleteRow(i);
}
}
}
Logging Output
[14-11-04 09:16:04:551 PST] Duplicate of ID 784 in sheet Sheet2 at row 2
[14-11-04 09:16:04:587 PST] Duplicate of ID 784 in sheet Sheet3 at row 2
[14-11-04 09:16:04:727 PST] Duplicate of ID 901 in sheet Sheet3 at row 5

How to automatically add a timestamp in google spreadsheet

I have a sheet in my Google spreadsheet that contains 5 cells, the first 3 contains only words while the last 2 contains time, specifically a timestamp.
cell 1 = data
cell 2 = data
cell 3 = data
cell 4 = time start
cell 5 = time ended
Now, what I want is when cell 1 is supplied with data, a timestamp will automatically appear in cell 4. And when cell 2 and cell 3 is supplied with data, a timestamp will be the new value for cell 5.
My friend give me a code, that should pasted in Script editor:
function readRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
Logger.log(row);
}
};
And
function onOpen() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Read Data",
functionName : "readRows"
}];
spreadsheet.addMenu("Script Center Menu", entries);
};
function timestamp() {
return new Date()
}
and this code is pasted in =IF(B6="","",timestamp(B6))cell 4 and this one =IF(D6="","",timestamp(C6&B6)) is on cell 5. in his example tracker its working. But when i copied it to mine, the output in cell 4 and cell 5 is the Date today and not the time.
can anyone help me? why does it output the date and not the time?
You can refer this tutorial, if this helps.
In the script code, change
var timestamp_format = "MM-dd-yyyy"; // Timestamp Format.
to
var timestamp_format = "MM-dd-yyyy hh:mm:ss"; // Timestamp Format.
This should probably help you.
I just came across this problem and I modified the code provided by Internet Geeks.
Their code works by updating a specified column, the timestamp is inserted in the same row in another specified column.
What I changed is that I separated the date and the time, because the timestamp is a string, not a date format. My way is useful for generating graphs.
It works by specifying the column to track for changes, and then creating an upDate and upTime columns for the date and time respectively.
function onEdit(event) {
var timezone = "GMT+1";
var date_format = "MM/dd/yyyy";
var time_format = "hh:mm";
var updateColName = "Резултат";
var DateColName = "upDate";
var TimeColName = "upTime";
var sheet = event.source.getActiveSheet(); // All sheets
// var sheet = event.source.getSheetByName('Test'); //Name of the sheet where you want to run this script.
var actRng = event.source.getActiveRange();
var editColumn = actRng.getColumn();
var index = actRng.getRowIndex();
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues();
var dateCol = headers[0].indexOf(DateColName);
var timeCol = headers[0].indexOf(TimeColName);
var updateCol = headers[0].indexOf(updateColName);
updateCol = updateCol + 1;
if (dateCol > -1 && timeCol > -1 && index > 1 && editColumn == updateCol) {
// only timestamp if 'Last Updated' header exists, but not in the header row itself!
var cellDate = sheet.getRange(index, dateCol + 1);
var cellTime = sheet.getRange(index, timeCol + 1);
var date = Utilities.formatDate(new Date(), timezone, date_format);
var time = Utilities.formatDate(new Date(), timezone, time_format);
cellDate.setValue(date);
cellTime.setValue(time);
}
}
Hope this helps people.
Updated and simpler code
function onEdit(e) {
var sh = e.source.getActiveSheet();
var sheets = ['Sheet1']; // Which sheets to run the code.
// Columns with the data to be tracked. 1 = A, 2 = B...
var ind = [1, 2, 3].indexOf(e.range.columnStart);
// Which columns to have the timestamp, related to the data cells.
// Data in 1 (A) will have the timestamp in 4 (D)
var stampCols = [4, 5, 6]
if(sheets.indexOf(sh.getName()) == -1 || ind == -1) return;
// Insert/Update the timestamp.
var timestampCell = sh.getRange(e.range.rowStart, stampCols[ind]);
timestampCell.setValue(typeof e.value == 'object' ? null : new Date());
}
I made a slightly different version, based also on the code from Internet Geeks
In order to support multiple named sheets, and because Google Sheets Script doesn't currently support Array.prototype.includes(), I included the polyfill mentioned here
Also, in my version, the timestamp marks the date of creation of that row's cell, not the date of the last update as in the other scripts provided here.
function onEdit(event) {
var sheetNames = [
'Pounds £',
'Euros €'
]
var sheet = event.source.getActiveSheet();
if (sheetNames.includes(sheet.getName())){
var timezone = "GMT";
var dateFormat = "MM/dd/yyyy";
var updateColName = "Paid for ...";
var dateColName = "Date";
var actRng = sheet.getActiveRange();
var editColumn = actRng.getColumn();
var rowIndex = actRng.getRowIndex();
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues();
var dateCol = headers[0].indexOf(dateColName) + 1;
var updateCol = headers[0].indexOf(updateColName) + 1;
var dateCell = sheet.getRange(rowIndex, dateCol);
if (dateCol > 0 && rowIndex > 1 && editColumn == updateCol && dateCell.isBlank())
{
dateCell.setValue(Utilities.formatDate(new Date(), timezone, dateFormat));
}
}
}
// https://stackoverflow.com/a/51774307/349169
// https://tc39.github.io/ecma262/#sec-array.prototype.includes
if (!Array.prototype.includes) {
Object.defineProperty(Array.prototype, 'includes', {
value: function(searchElement, fromIndex) {
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
// 1. Let O be ? ToObject(this value).
var o = Object(this);
// 2. Let len be ? ToLength(? Get(O, "length")).
var len = o.length >>> 0;
// 3. If len is 0, return false.
if (len === 0) {
return false;
}
// 4. Let n be ? ToInteger(fromIndex).
// (If fromIndex is undefined, this step produces the value 0.)
var n = fromIndex | 0;
// 5. If n ≥ 0, then
// a. Let k be n.
// 6. Else n < 0,
// a. Let k be len + n.
// b. If k < 0, let k be 0.
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
function sameValueZero(x, y) {
return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
}
// 7. Repeat, while k < len
while (k < len) {
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
// b. If SameValueZero(searchElement, elementK) is true, return true.
if (sameValueZero(o[k], searchElement)) {
return true;
}
// c. Increase k by 1.
k++;
}
// 8. Return false
return false;
}
});
}

Google Apps Script spreadsheet date manipulation

I have three scripts that are in a google docs spreadsheet. In this spreadsheet, in column H (or column 8), if I type an "x", the script changes it into that days date. After a few days, every date in column H has changed from a date to just a number. The numbers look like this: 40492, 40494, 40511. I am not sure what is causing this. Maybe it's something that is wrong in my script. I've pasted them below. Any ideas?
function onEdit(e) {
var colorA = "yellow";
var colorB = "#dddddd";
var colorC = "#dddddd";
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Purchase Orders");
var range = e.source.getActiveRange();
var sheetName = SpreadsheetApp.getActiveSheet().getName();
if (sheetName == "Purchase Orders") {
// 3 is column C
if (range.getColumn() == 3 && range.getValue() != "") {
sheet.insertRowAfter(range.getRow());
var r = range.getRow() + 1;
sheet.getRange("A" + r + ":H" + r).setBackgroundColor(colorC);
}
}
var col = e.source.getActiveRange().getColumn();
if(col == 3 || col == 8) {
var rows = sheet.getMaxRows();
//column C
var rangeC = sheet.getRange("C1:C"+rows);
var valuesC = rangeC.getValues();
//column H range
var rangeH = sheet.getRange("H1:H"+rows);
var colorH = rangeH.getBackgroundColors();
var valuesH = rangeH.getValues();
//iterate over each row in column C and H
//then change color
for (var row = 0; row < valuesC.length; row++) {
//check for columnC and column H
var hRow = colorH[row];
if (valuesC[row][0] != "" && valuesH[row][0] == "") {
hRow[0] = colorA;
} else if (valuesH[row][0] != "") {
hRow[0] = colorB;
}
}
sheet.getRange("H1:H" + rows).setBackgroundColors(colorH);
}
}
And this one
function onEdit(e) {
var ss = e.source.getActiveSheet();
var r = e.source.getActiveRange();
// 1 is A, 2 is B, ... 8 is H
if (r.getColumn() == 8 && r.getValue() == "x") {
r.setValue(Utilities.formatDate(new Date(), "MST", "yyyy-MM-dd"));
}
}
And this last one
ss = SpreadsheetApp.getActiveSpreadsheet();
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [ {name: "New PO", functionName: "NewPO"}];
ss.addMenu("New PO", menuEntries);
}
function NewPO() {
SpreadsheetApp.getActiveSheet().insertRowsBefore(1,6);
// Adjust this range accordingly, these are the cells that will be
// copied. Format is getRange(startRow, startCol, numRows, numCols)
ss.getSheetByName("PO Form").getRange(1, 1, 6, 8)
.copyTo(SpreadsheetApp.getActiveSheet().getRange(1, 1, 6, 8));
}
In OnEdit, you probably want to set the format for that cell as well. setNumberFormat(numberFormat) appears to be the function you are after.
http://code.google.com/googleapps/appsscript/class_range.html#setNumberFormat