I am at my wit's end here. I had three scripts working beautifully, and then overnight they all failed with the dreaded "Service Error".
The error always references getLastRow as the offending line. I've tried adding and removing rows at the bottom of the sheet, removing some of the rows with data in them, unfreezing headers and columns, etc. with no luck whatsoever. I also tried reverting back to last week's version of the script, long before the failure (first was Monday night), to no avail.
I've done a lot of research and I've noticed most often the thing that fixes these Service Errors is something completely random. Some people say that this error is caused by memoization on Google's end. If so, what can I do to fix this?
A huge project is basically crashing and burning because these scripts failed...any help would be awesome. Thanks.
For reference, one of my scripts (the one that failed first).
function timeStamp2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[4];
var numRows = sheet.getLastRow()-1; // Number of rows to process
Logger.log(numRows)
var startRow = 2; // First row of data to process
// Fetch the range of cells
var dataRange = sheet.getRange(startRow, 1, numRows, sheet.getLastColumn());
// Fetch values for each row in the Range.
var data = dataRange.getValues();
//Logger.log(data)
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var schedulingStatus = row[4];
var timeStamp = row[3];
var scheduled = "Scheduled";
if(timeStamp === '') {
if(schedulingStatus == scheduled ) {
sheet.getRange(startRow + i, 3).setValue(new Date());
}
}
}
}
Whether this constitutes an 'Answer' I cannot judge, but rest assured you are not alone in noticing the sporadic behavior of certain intrinsic GAS functions – notably a few in common use with spreadsheets to append new rows or locate the last row/column of data – which has plagued my own code and others' due to extremely long execution times, often as high as 20 seconds per instance!
I insist there must be a hidden timing flaw in getLastRow(), as shown below. The problem now routinely leads to Timeouts in my workflow and generates frequent failures and Service error: Spreadsheets messages.
For example, if you run the following code function every minute (via a timed Trigger)...
function getLastRow_TEST() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("getLastRow_TEST");
var timeBegin = Date.now();
var nextRow = sheet.getLastRow() + 1;
var timeEnd = Date.now();
var range = sheet.getRange("A" + nextRow + ":B" + nextRow);
var timeElapsed = (timeEnd - timeBegin) / 1000;
range.setValues([[nextRow - 1, timeElapsed]]);
}
...on your test Spreadsheet, containing a sheet named 'getLastRow_TEST', you will soon notice clustered delays (> 1 sec.), as shown in the resulting data (collected at 10:55am on Monday 6/13/2016), but with no discernible pattern of occurrence:
TEST # SECONDS
1 0.041
2 10.242 *
3 10.256 *
4 5.194 *
5 0.055
6 0.178
NOTE: This testing was performed with an otherwise pristine (empty) spreadsheet, with no other code running, no timed Triggers, nor script locking active.
I am attempting to submit this issue as a bug report on Google's issues tracker, but felt compelled to share my experience with you here (I posted elsewhere on SO about this glitch).
Are you counting your array positions correctly?
ss.getSheets()[4]
means it's the fifth sheet. I also noticed that although you initialise timeStamp as the value in the fourth column of row:
var timeStamp = row[3]
later on you:
sheet.getRange(startRow + i, 3).setValue(new Date())
in the third column of the row. ie row[3] is one cell to the right of sheet.getRange(startRow + i, 3).
I had a similar issue with a script I wrote.
It gave Service Error: Spreadsheets said the problem was with the line containing getLastRow.
My fix ended up making sure there was at least one row at the bottom without any data. Haven't had a problem since.
I had the same problem recently.
My attempts to fix would be:
Try to replace getLastRow with getMaxRows — get the last
row of a sheet
Try to use the construction like:
sheet.getDataRange().getLastRow(); → assuming my data range starts
at row #1, the result should be the same.
Related
I have a script to paste the raw data from a csv received by email. When the raw data is pasted on the sheet, I expected that another sheet with a query import range formula updates automatically with the new data.
I have a second script to read data from a pivot table that comes from the sheet with those formulas. However when it tries to read the data from the pivot table I get the error Exception: The number of rows in the range must be at least 1.. This happens because my variable numRows is equal to zero.
When I open the g-doc manually I see an error on the sheet with the formulas mentioned: error loading.
However, after really a few seconds that I open the gdoc, the range updates almost instantaneally without any problem, and If I manually run the script after this happening it runs without any problem.
How can I make sure that after updating the raw data I don't get the formulas stucked on error loading? I would like to run the script automatically and not manually. Any tip is more than welcome.
Notes:
I've tried already every type of recalculations but didn't work (on change, on change and every hour, on change and every minute)
The raw data has arround 2300 rows
The formula I am using is the following: =QUERY(IMPORTRANGE("1OpF8gcrV1Yj8bYP1j5PsHM4VRw2pKZOUmJf6VxGeFdY","raw_data!A2:G"), "select Col1,Col2,Col3,Col4,Col5,Col6,Col7 where Col2 is not null order by Col4 asc, Col1 asc, Col5 asc",0)
function sending_emails(){
var ss=SpreadsheetApp.openById("1OpF8gcrV1Yj8bYP1j5PsHM4VRw2pKZOUmJf6VxGeFdY");
var today = new Date();
if(today.getDay() != 6 && today.getDay() != 0){
//Sending emails to reps:
var data_sheet = ss.getSheetByName("Copy of sending_emails");
var aux = data_sheet.getRange("B3:B").getValues();
var startRow = 3; // First row of data to process
var numRows = aux.filter(String).length;
Logger.log('numRows' + numRows);
// Fetch the range of cells
var dataRange = data_sheet.getRange(startRow, 1, numRows, 5); //I get the error here because startRow = 3 and I get numRows = 0
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var a in data) {
var row = data[a];
var message = row[3];
var emailAddress = row[0];
Logger.log('emailAddress'+ emailAddress);
MailApp.sendEmail({
to: emailAddress,
subject: 'Task Manager',
htmlBody: message,
cc: row[4]
});
}
}
}
The issue is likely with IMPORTRANGE
The class of functions IMPORTHTML, IMPORTRANGE etc have been the subject of many questions about auto updating - this approach generally seems to be quite flaky. I can't find it documented anywhere but I suspect that these functions stop calculating when they are closed. Or if a recalculation happens, for some reason they are not authorized because they are no longer linked to a user session.
That said, although I don't use this approach, I have tested it various times and it seems to work for me, though I know there are many people for whom it does not.
Some people have found that by removing all protections and making the sheet public removes errors, though in my experience its just best to remove formulae from the equation (no pun intended).
Suggested fix
In your chain of Mail > Apps Script > Sheet > FORMULA > Sheet.
Change it to Mail > Apps Script > Sheet > Apps Script > Sheet.
I don't have your source data to test with, but to implement your query in Apps Script would look something like this:
const ss = SpreadsheetApp.openById("YOUR ID");
const dataRange = ss.getSheetByName("Sheet1").getRange("A2:G");
const data = dataRange.getValues()
const filteredData = data.filter(row => row[1] !== "")
You could potentially sort the data with formulae once it has been imported with the script.
TLDR: Chaining IMPORTRANGE may work sometimes, but it doesn't seem very reliable. In my opinion, you are better off moving everything to Apps Script at this point.
My goal is to automate adding 10 rows to a Google Sheet used by a nonprofit organization's business and then replicate the formulas sequences needed in the newly created rows. I had all the code working to accomplish the task and prevent users from messing up the spreadsheet formulas when they manually insert rows. However, the code time out due to the number of rows in the spreadsheet with the looping use of getRange(). My new approach is to jump to a named cell as a starting point instead of the the really slow looping cell search.
I have created a name "EndData", read all the stuff I can find online, trialed and errored the syntax for hours to get the named_cell range into myrange and then activate the range on the worksheet...
Here is the current coding attempt (which leaves the cursor at the top of the column and an
"TypeError: Cannot find function getRangeByName in object Sheet. (line 170, file "macros")"
//Get EOD range, select, index up 3 rows to start row insertions
function getEOD() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
Logger.log(ss); //lOG is not helpful, says, "sheet", not SheetName
var MyRange = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRangeByName("EndData");
Logger.log(MyRange); //lOG is not helpful, says, "Range", not RangeAddress
//Activate the named cell, moves with the spreadsheet
MyRange.activate();
};
Had a new idea after I asked for help, here's the working code that gets the job done:
//Get EOD range, select, index up 3 rows to start row insertions
function getEOD() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//Logger.log(ss); //lOG is not helpful, says, "sheet", not SheetName
var rg = SpreadsheetApp.getActiveSpreadsheet().getRangeByName('EndData');
if (rg != null) {
Logger.log(rg.getNumColumns());
}
//Logger.log(rg); //lOG is not helpful, says, "Range", not RangeAddress
//Referenced the Named the EOD cell
//Future, Trying to create a debug status bar
//SpreadsheetApp.getActiveSpreadsheet().toast("looking at row" & i "now")
//Activate the named cell, which moves with spreadsheet growth, down
rg.activate();
//Uncomment for testing purposes, places a Yes on the row 4 columns over
//sheet.setActiveRange(sheet.getRange(0, 4)).setValue('Yes');
//turned off durning testing, writes in data range with this trial code
//Reposition from named cell to insert lines location
ss.getRange(sheet.getCurrentCell().getRow() -2, 1, 1,
sheet.getMaxColumns()).activate();
//Insert ten lines, copy and paste special formulas only
Insert_10_Lines()
//https://stackoverflow.com/questions/59772934/get-range-from-
getrangebyname-activate-range
};
My answer is I have tenacity, I try to limit the variables one at a time to prove how things really work, moved the code that did work in the test script file to a production script file and it didn't work for copy of what I had in test, went back to the test script and it didn't work either, after it had... There are variables in play that seem to be happening at the scripting, savings and running steps that change the interactive responses I am getting. Trial and error again through the production environment came up with a working combination by going to the simples code suggested and getting a combination that works. Here is the the code that is running in product to the main need of my question...
function InsertRows() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//Get range from named cell
var range = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("EOD");
if (range != null) {
Logger.log(range.getNumColumns());
}
//Select the cell as a starting point for code to follow
range.activate(); //start location cell is active and row is selected
Thanks for those that responded for the help! Looks like it is going to take a while to recognize the patterns and figure out what to do to get consistent results in a timely manner...
Try this:
function getEOD() {
var ss=SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var MyRange=ss.getRangeByName("EndData");
MyRange.activate();
}
Were not set up to use a proper SQL database or anything so were working with google sheets.
I've been trying to avoid importrange as I have a large amount of data constantly being updated and more rows added to Form responses every day. Importrange constantly fails with "importrange internal server error"
I found this fantastic code to copy from one source spreadsheet to another (as static text) so I can further manipulate the data :
function CopyTaskSource() {
var sss = SpreadsheetApp.openById('1OPnw_7vTCFkChy8VUKhAG5QRhcpKnDbmod0ZxjG----'); //replace with source ID
var ss = sss.getSheetByName('TASK Status'); //replace with source Sheet tab name
var range = ss.getRange('E:L'); //assign the range you want to copy
var data = range.getValues();
var tss = SpreadsheetApp.openById('1T3tqsHvKxuulYxDnaR3uf-wjVdXwLHBcUgI7tgN----'); //replace with destination ID
var ts = tss.getSheetByName('TaskSource'); //replace with destination Sheet tab name
ts.getRange(1, 1, data.length, data[0].length).setValues(data); //you will need to define the size of the copied data see getRange()
}
Now it copies about 15,000 rows of data, and I expect I will end up at 50,000 rows of data (and some other sheets go up to 27 columns).
I started getting this Service error: Spreadsheets line 9 (last line of the code).
Can someone please advise me a workaround to get bulk data transferred to multiple Google spreadsheet files?
importrange doesn't work well, and I have a few Google Forms that I need to combine the source responses to manipulate the data to output presentable spreadsheets.
Thank you
So I am working currently on a script that sends out emails when there is an issue, it then adds an array of values ,containing three values [type, ID, status], to an existing array ending with [[values1],[values2],etc...].
I have gotten the same error when I leave the third parameter of getRange as the array.length. I got it to work once yesterday by subtracting the array.length by 1 as I will show below. Maybe you can try this on line 9 and see if that fixes it?
It is important to mention that today after running the exact same script, it gave me an error stating that the range size was incorrect (due to the same subtraction that seemed to fix the service error)
I think that it may be broken on Google's side, but that is not something I can confirm.
This:
ts.getRange(1, 1, data.length, data[0].length).setValues(data);
Becomes This:
ts.getRange(1, 1, data.length - 1, data[0].length).setValues(data);
Hope that fixes it for you, I am truly stumped as to why it decides to work one day but not another...
I also added a waitLock to make sure it waits for other processes to be finished before trying to write it, but realize the data I write is much smaller, only 3 columns by 6-10 rows at a time. Here is the code for that, though this is to insert the data at the top of the sheet, not the bottom. (From Henrique G. Abreu, Original Post)
function insertRow(sheetI, rowData, optIndex) {
var lock = LockService.getScriptLock();
lock.waitLock(30000);
try {
var index = optIndex || 1;
sheetI.insertRowsBefore(index,rowData.length).getRange(index, 1, rowData.length, 3).setValues(rowData)
SpreadsheetApp.flush();
} finally {
lock.releaseLock();
}
}
I am trying to adapt the example script from this previous, related question. For rows where the cell value in column K is zero, I want to make the row yellow.
Here is my current adapted code:
function colorAll() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 3;
var endRow = sheet.getLastRow();
for (var r = startRow; r <= endRow; r++) {
colorRow(r);
}
}
function colorRow(r){
var sheet = SpreadsheetApp.getActiveSheet();
var c = sheet.getLastColumn();
var dataRange = sheet.getRange(r, 1, 1, c);
var data = dataRange.getValue();
var row = data[0];
if(row[0] === "0"){
dataRange.setBackground("white");
}else{
dataRange.setBackground("yellow");
}
SpreadsheetApp.flush();
}
function onEdit(event)
{
var r = event.source.getActiveRange().getRowIndex();
if (r >= 3) {
colorRow(r);
}
}
function onOpen(){
colorAll();
}
My problem is, I can't figure out how to reference column K. In the linked answer above, the script's creator claims, "[h]ere is a Google Apps Script example of changing the background color of an entire row based on the value in column A." First, and most importantly, I can't figure out where he's referencing column A. I thought changing "var dataRange = sheet.getRange(r, 1, 1, c);" to "var dataRange = sheet.getRange(r, 11, 1, c);" would do it, but that just added 10 blank columns to the end of my sheet, and then the script crashed. I do not understand why.
Secondly, but more as an aside, his claim that the script affects entire rows is inaccurate, as his original "var dataRange = sheet.getRange(r, 1, 1, 3);" only colored the first three columns - which is why I added "var c" and changed "3" to "c".
Furthermore, when I play/debug the script, or run "onEdit" from the spreadsheet script manager, I get "TypeError: Cannot read property "source" from undefined." I can see that "source" is undefined - I had mistakenly assumed it was a Method at first - but I'm not sure how to fix this issue either.
Lastly, column K will not always be the reference column, as I mean to add more columns to the left of it. I assume I'll have to update the script every time I add columns, but there is a column heading in row 2 that will never change, so if someone can help me devise a bit of code that will look for a specific string in row 2, then get that column reference for use in function colorRow(), I would appreciate it.
I can't tell if this script is structured efficiently, but ideally, I want my spreadsheet to be reactive - I don't want to have to rerun this script after editing a driving cell, or upon opening; it reads like it's supposed to do that (were it not buggy), but this is my first attempt at using Google Apps Script, and I don't feel certain of anything.
I'm not great with scripting, but I took a programming fundamentals/Python class in grad school back in 2006, and spent 4 years working with Excel & Access shortly after that, often creating and adapting Macros. I can't really design from scratch, but I understand the basic principles and concepts, even if I can't translate everything (e.g., I don't understand what the "++" means in the third argument in the "for" statement I'm using: "for (var r = startRow; r <= endRow; r++)." I think I'm allegorically equivalent to a literate Spanish speaker trying to read Italian.
Help, and educational explanations/examples, will be much appreciated. Thank you kindly for reading/skimming/skipping to this sentence.
Rather than rewriting the code which you have already got some help with, I will try to give you explanations to the specific questions you asked. I see that you have some of the answers already but I am putting thing in completely as it helps understanding.
My problem is, I can't figure out how to reference column K.
Column A = 1, B = 2,... K = 10.
I can't figure out where he's referencing column A.
You were close when you altered the .getRange. .getRange does different things depending on how many arguments are in the (). With 4 arguments it is getRange(row, column, numRows, numColumns).
sheet.getRange(r, 1, 1, c) // the first '1' references column A
starts at row(r) which is initially row(3), and column(1). So this is cell(A3). The range extends for 1 row and (c) columns. As c = sheet.getLastColumn(), this means you have taken the range to be 1 row and all the columns.
When you changed this to
var dataRange = sheet.getRange(r, 11, 1, c) // the '11' references column L
You have got a range starting at row(3) column(L) as 11 = L. This runs to row(3) column(getLastColumn()).
This is going to do weird things if you have gone out of range.
You may have pushed it in to an infinite for loop which would cause the script to crash
Secondly, but more as an aside, his claim that the script affects entire rows is inaccurate, as his original "var dataRange = sheet.getRange(r, 1, 1, 3);"
only colored the first three columns - which is why I added "var c" and changed "3" to "c".
You are correct. The (3) says that the range extend for 3 columns.
"TypeError: Cannot read property "source" from undefined."
What is happening here is not intuitively clear. You can't run the function onEdit(event) from the spreadsheet script manager because it is expecting an "event".
onEdit is a special google trigger that runs whenever any edits the spreadsheet.
it is passed the (event) that activated it and
event.source. refers to the sheet where the event happened so
var r = event.source.getActiveRange().getRowIndex(); gets the row number where the edit happened, which is the row that is going to have its color changed.
If you run this in the manager there is no event for it to read, hence undefined. You can't debug it either for the same reasons.
Lastly, column K will not always be the reference column, as I mean to
add more columns to the left of it. I assume I'll have to update the
script every time I add columns, but there is a column heading in row
2 that will never change, so if someone can help me devise a bit of
code that will look for a specific string in row 2, then get that
column reference for use in function colorRow(), I would appreciate
it.
Before I give you code help her, I have an alternative suggestion because you are also talking about efficiency and it is often faster to run functions in the spreadsheet than using scripts. You could try having column A as an index columns where ColumnA(Row#) = ColumnK(Row#). If you put the following into cell(A1), ColumnA will be an exact match of Column K.
=ArrayFormula(K:K)
Even better, if you add/remove Columns between A and K, the formula will change its reference without you doing anything. Now just hide columnA and your sheet is back to its originator appearance.
Here is your code help, utilizing some of your own code.
function findSearchColumn () {
var colNo; // This is what we are looking for.
var sheet = SpreadsheetApp.getActiveSheet();
var c = sheet.getLastColumn();
// gets the values form the 2nd row in array format
var values = sheet.getRange(2, 1, 1, c).getValues();
// Returns a two-dimensional array of values, indexed by row, then by column.
// we are going to search through values[0][col] as there is only one row
for (var col = 0; col < data[0].length; col++) { // data[0].length should = c
if (data[0][col] == value) {
colNo = col;
break; // we don't need to do any more here.
}
}
return(colNo);
}
If break gives you a problem just delete it and let the look complete or replace it with col = data[0].length;
I can't tell if this script is structured efficiently, but ideally, I
want my spreadsheet to be reactive - I don't want to have to rerun
this script after editing a driving cell, or upon opening; it reads
like it's supposed to do that (were it not buggy), but this is my
first attempt at using Google Apps Script, and I don't feel certain of
anything.
It is ok, the fine tuning of efficiency depends on the spreadsheet. function onEdit(event)
is going to run every time the sheet is edited, there is nothing you can do about that. However the first thing it should do is check that a relevant range has been edited.
The line if (r >= 3) seems to be doing that. You can make this as specific as you need.
My suggestion on a hidden index column was aimed a efficiency as well as being much easier to implement.
I'm not great with scripting,
You are doing ok but could do with some background reading, just look up things like for loops. Unfortunate Python is grammatically different from many other languages. A for loop in google script is the same as VBA, C, JAVA, and many more. So reading about these basic operations is actually teaching you about many languages.
I don't understand what the "++" means in the third argument in the "for" statement
It is why the language C++ gets its name, as a programmer joke.
r++ is the same as saying r = r+1
r-- means r = r-1
r+2 means r = r+2
So
for (var r = startRow; r <= endRow; r++)
means r begins as startRow, which in this case is 3.
the loop will run until r <= endRow, which in this case is sheet.getLastRow()
after each time the loop runs r increments by 1, so if endRow == 10, the loop will run from r = 3 to r = 10 => 8 times
1.The onEdit is a special function that is automatically called when you edit the spreadsheet. If you run it manually, the required arguments won't be available to it.
2.To change the colour of the entire row when column K is 0, you have to make simple modifications to the script . See below
function colorRow(r){
var sheet = SpreadsheetApp.getActiveSheet();
var c = sheet.getLastColumn();
var dataRange = sheet.getRange(r, 1, 1, c);
var data = dataRange.getValues();
if(data[0][10].toString() == "0"){ //Important because based on the formatting in the spreadsheet, this can be a String or an integer
dataRange.setBackground("white");
}else{
dataRange.setBackground("yellow");
}
SpreadsheetApp.flush();
}
I have a google sheet that checks which cell is being changed, and if it falls in the correct range, then it updates the date in the last column out. The code looks like this:
function onEdit() {
var firstProductCol = 6;
var lastProductCol = 30;
var s = SpreadsheetApp.getActiveSheet();
if( s.getName() == "Skills Map" ) { //checks that we're on the correct sheet
var activeCell = s.getActiveCell();
Logger.log("activeCell: " + activeCell.getA1Notation());
var seName = activeCell.offset(0, 3 - activeCell.getColumn()).getValue();
Logger.log('value of B: [' + seName + "]");
}
And the error suggests it's on Line 7 which is just a getActiveCell... so I'm not sure how that can cause an error to be thrown given that if there isn't an active cell, you can't be changing the value of the field.
When I run the script myself, I never run into the error. The problem is every once in a while, when others use the sheet (I don't know what they do in particular), I receive an end of day report on this error...
Using the debugging tools in Script Editor, I can only see the logs from the last run... although there is a View -> Console Logs, but that doesn't seem to store any of the logs from previous runs... any way to do that?
Any suggestions on the error itself?
Cheers.
Looks that the line number 7 is
var seName = activeCell.offset(0, 3 - activeCell.getColumn()).getValue();
Since column number should be a positive numbers it's very likely that the error occurs when the user edit a cell other than the columns A, B or C.
One way to handle this is to check that the value that returns activeCell.getColumn() is less than 3.