The coordinates of the range are outside the dimensions of the sheet (google-script) - google-apps-script

I have a really very odd problem, which seems to have to do with the sequence i execute scripts. After investigating for hours, I cant explain it at all.
I have a google sheets script which gets emails from an email account and parses them according to given rules into a speadsheets.
I have many of those methods, all leveraging common classes like getEmails, etc.
Every single method of parsing works well and delivers the respected result. But when I run them in a big method one after the other it reports the error
"The coordinates of the range are outside the dimensions of the sheet."
after executing some of the methods correctly. The error occurs in the following line:
var resultArray = sheet.getRange(startrow, column, sheet.getLastRow(), 1).getValues();
and is based on the call
sheet.getLastRow()
(I can not even call this in the logger, it works for lets say 5 out of the 10 methods and then all the sudden i get the error)
Every of those methods parse a different email with a different pattern but does this only for new emails. Therefore I have to get the hashs of the old emails (thats the call) from the google sheets column 1 to work only on new email hashes. This process breaks somehow.
What is striking me is that i can execute any of the methods isolated without an error.
Any ideas?
As mentioned I have tried isolated and i have tried to change order or to run only 2 of the methods.. with the same result. I assume some variable is not set back properly... but i have no idea how that can lead to this error.
By the way: the code was working for the past few weeks without error (also for the combined method). The errors have started like a week ago without any code changes.

I came across the same issue.
This is a sample of the code that was causing the bug for me:
var priceSheet = ss.getSheetByName(priceSheetName);
var rangeToSort = priceSheet.getRange(2,1,priceSheet.getLastRow(),priceSheet.getLastColumn());
rangeToSort.sort(1);
ss.getSheetByName("my sheet").getRange(startingRow,pasteColumn,pasteHoldings.length,1).setValues(pasteHoldings);
The error The coordinates of the range are outside the dimensions of the sheet was raised on line 4 (similar to your scenario), but the issue was occurring when I was trying to sort a range that extended beyond the last row of the sheet i.e.
last row with contents = last row of the sheet
Sort row start range = 2
Number of rows = priceSheet.getLastRow() <-- this is impossible because the sort row start range is great than 1
The fix for me was to adjust the sort range down by the start row - 1
var priceSheet = ss.getSheetByName(priceSheetName);
var rangeToSort = priceSheet.getRange(2,1,priceSheet.getLastRow()-1,priceSheet.getLastColumn());
rangeToSort.sort(1);
ss.getSheetByName("my sheet").getRange(startingRow,pasteColumn,pasteHoldings.length,1).setValues(pasteHoldings);
This appears to be a bug in Google Sheets script: either the sort functionality, or at the very least the error handling is raising the error with reference to the wrong row.
My recommendation would be to check your use of .getLastRow() and see if it corresponds to a starting row greater than 1. Then adjust the .getLastRow() by starting row - 1

Would you happen to have your sort range starting on something other than row 1?
I was testing this out and I have my data to sort on A7:N100.
What I found was that the max row that I can have is the last row in the range minus the header rows that are not in the range. For example, I have the first 6 rows that aren't in the range. I have my last row in row 100. So my range is only working with A7:N94.
To solve, I ended up adding 6 blank rows to the bottom of my page and set sort range to what I wanted (A7:N100) and this worked.

Related

Apps script inserts new row and values into a Google Sheet. However, it somehow skips rows if you submit too fast

I have a server side apps script that inserts a new row into a Google sheet, and then adds data to corresponding columns. Here is a short code snippet of what it does:
//check to see what is the last row of the sheet
var last_row = sheet.getLastRow();
//then, insert a new row after the last detected row
sheet.insertRowAfter(last_row);
//then, set current work row to last_row + 1, then do some adding data.
//Here, we add a current_timedate Date object that was created earlier.
var current_row = last_row + 1;
var current_timedate_cell = 'A' + current_row;
sheet.getRange(current_timedate_cell).setValue(current_timedate);
This is all pretty standard stuff. However, I noticed if I call the function that does all this pretty quickly from the front end interface (I have another client side javascript / html interface that calls the server side javascript function with onClick method on a button), it starts to skip rows. There also seems to be some bizarre concurrency issues. Some rows actually have partial data inserted, but only for checkboxes that I'm adding. None of the other stuff gets added. I can't tell if there is mixing of data between rows since all of the data is repeated. So, I can't tell if row 2's column H is now in row 3's column H, etc... To be fair, I am inserting one LARGE cell per row up to the 50k character limit. So, each row is probably close to 50k. That could be causing these issues
Can anyone provide some insights? I know Google Sheets is not supposed to be a relational database with ACID integrity and concurrent performance. But, I thought it would do a better job of handling 50kb inserts.
I've tried SpreadsheetApp.flush(). I'm somewhat aware of the rate limitations, and perhaps that might be it. I'm sometimes doing the inserts at a rate of about 3 inserts / second. I'll go double check quota rate limits. But, 3 / second doesn't seem incredibly high.
Expected results: all data requested to be inserted is inserted
Actual results: lots of skipped rows on faster insert rates
I'm doing all of this in a try / catch block, and so far I'm not getting any errors thrown. If there is an error thrown, I both log it in Logger and email it to myself via the MailApp.
Just going to add it as an answer instead of comment since it answered the question:
Use appendRow() instead of insertRowAfter() for this purpose.

Limit the number of row in Google Sheets

I have a google sheets document that keeps adding rows to the sheet. It slows down my document tremendously because of scripts I run against the document. I only need around 100 rows, but it continues to add several hundred to a thousand plus. Is there a way to limit this with a hard number or set a script to automatically delete any row after 100.
This is a log of orders I am maintaining.
I had considered using a script to
getMaxRows() - getLastRow() + 25 //for future blank rows.
However, I could not get it to function properly. My scripting abilities are limited.
Something akin to.
var Max = getMaxRows()
var Last = getLastRows()
start = 2;
end = Max-Last+25
sheet.deleteRows(start, end);
Issue:
You're probably using a badly written auto-iterating array formula function, where the end result of the formula is more than the number of cells in the sheet. The function will automatically create space for the result by inserting rows.
Example:
A simple example of a self-iterating arrayformula is provided below. In a "1000 row" new sheet, If you type in this formula, New rows will be inserted upto a total of 50,000 rows, but it varies depending on the device/ browser you use.
A1:
=ARRAYFORMULA(ROW(A1:A1001))
Formula Explanation:
ROW(A1:A1001) creates a array of 1 to 1001 and returns them to the sheet
But there's no space in the sheet
One more row is needed
Sheets automatically adds 1 row after row 1000
But, If a row is inserted after A1000, The reference A1001 in ROW(A1:A1001) automatically becomes A1002, because a new row is added between A1:A1001.
Now, The formula says, =ARRAYFORMULA(ROW(A1:A1002)), so, 1002 numbers, So, we still need a extra row. A new row is added, reference changes again, a new row is needed again and so forth until a hard limit like 50,000 is reached and your formula stops iterating and becomes a #REF error.
Solution(s):
Create a copy of your sheet and delete each array formula one by one followed by deleting the rows each time to pin the rogue formula.
Pay attention to expanding array formulas like those using SPLIT.
Use sane arrayformula methods:
Avoid: IF(ROW(A1:A100)=1,"Header",...) Use: {"Header";...}
Avoid: Open ended ranges; Use Close ended ranges using INDEX/COUNTA

How to get actual position of range, when new row(s) above was added

consider this situation.
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("2:2");
var values = range.getValues();
values[0][0] = "updated value"; //updating retrieved data
sheet.insertRowAfter(1); //here the problem appears
range.setValues(values); //trying to save updated row data - FAIL!
Updated row values will be written in a row number 2 (as per range selector "2:2") not in new position "3:3".
Result: It will overwrite data at row 2 instead of updating previously selected/loaded row, which is now at position "3:3" and not anymore at "2:2".
Im looking for a way how to keep "address" of a range even when the range was shifted because of added/removed rows/cells somewhere else in the sheet.
In this dummy example I can track range changes. But in parallel processing/updating sheet i can't simply track all changes from many different places.
So far I came up with a solution add new rows only at the end of SS (that doesn't change range of any rows above, but I would like to have ability add new rows on top of sheet without influencing already selected ranges.
Deleting rows is also dangerous situation - it changes position of ranges also.
LockService can't really solve the situation, because I have plenty scripts working on the same sheet (not using centralized library because of speed performance in addon).
Metadata for cell or row seems too complicated to handle for such an easy task.
From my point of view Object Range should keep its position even when its moved/shifted somewhere else. Otherwise I can't see reason to have Range as an Object - if it keeps only fixed information about from where it was picked.
Any advices are welcomed. Thank you in advance..
EDIT:
Just to add a context. I'm using Google sheets as a database for orders (10 thousands so far) - each row means one order (customer) and not all orders are in one sheet - different products have different sheets (+- 10 products/sheets)
There is an suggestion using named ranges to solve this issue - So what will happen if a spreadsheet will have ten of thousands named ranges - can that work without serious performance issues? Im thinking about to creating named range for each order row, so I can easily pick up right row by orderId and not to be afraid of moving a row when new order arrive during processing another one
You want to achieve the following flow.
Retrieve the value from "2:2" (row 2).
Modify the retrieved value.
Insert a row just below of the row 1.
By this, the initial row 2 is moved to the row 3.
Put the retrieved value to the row 3 which is the initial row 2.
You want to achieve this using Google Apps Script.
If my understanding is correct, how about this answer? Please think of this as just one of several answers.
Issue and solution:
In your script, range of var range = sheet.getRange("2:2"); is the constant. By this, even when the row is inserted, this value is not changed. This is the reason of your issue.
I think that the idea using the named range will be resolved for your situation. But your comment says as follows.
I just found, that namedRange is not capable actualize its own address, if something had changed in the sheet after initialize of a range.
When the row 2 is installed as the named range, when a row is inserted to just below of the row 1, the range of the named range is also changed. But from your comment, I thought that the named range might have not been reloaded.
In order to confirm this, I would like to propose a sample script.
Sample script:
Before you run the script, please install a named range to row 2 as the name of sampleNamedRange. Then, run this script.
var ss = SpreadsheetApp.getActiveSpreadsheet(); // Added
var sheet = ss.getActiveSheet(); // Modified
// Retrieve the range from the namedRange.
// namedRange of "sampleNamedRange" is installed for the range of "2:2" of the active sheet.
var range = ss.getRangeByName("sampleNamedRange"); // Modified
var values = range.getValues();
values[0][0] = "updated value"; //updating retrieved data
sheet.insertRowAfter(1);
// Retrieve the range from the updated namedRange.
var range = ss.getRangeByName("sampleNamedRange"); // Added
range.setValues(values);
Result:
Note:
The point is to retrieve the range from the updated named range. By this, the updated range can be used. So in above script, the updated value is put to the row 3.
References:
getRangeByName()
Class NamedRange
If I misunderstood your question and this was not the result you want, I apologize

insertRowBefore the last Row returning error

I have a script that (gross, I know) writes to a spreadsheet. It dynamically adds and deletes rows based on input data from another sheet. I like doing this because the user has to copy and paste the resulting data, formatted into a table, into an email after (Let's skip creating an email directly from this script for now, that's part two).
I figured the easiest way to let the script know where to paste new data is to always insert a row before the very last row of the spreadsheet and paste said data.
I have been using getMaxRows(), getLastRow(), and insertRowBefore(), but all combos of these functions returns an "those rows are out of bounds" error!
I really don't understand what's going on. I use logs and see the variables getLastRow() and getMaxRows both get updated after I've done some copying of a template table to the sheet which then "inserts" rows. For some reason, even after re-calling the getMaxRows() and getLastRow() functions, they don't recognize the new rows. The script can still manipulate these new rows.
This leads me to believe getMaxRows() and getLastRow() can only get info about the number of rows in the sheet only when the script first runs, or only when the sheet is first activated? Has anyone faced this issue before? Does anyone have details about how those two functions work? The GAS 'sheet' class page didn't help.
spread = SpreadsheetApp.getActiveSpreadsheet();
emailsSheet = spread.getSheetByName("emails");
emailsMaxRows = emailsSheet.getMaxRows(); //# of rows regardless of content.
emailsSheet.deleteRows(2,emailsMaxRows-1); //I want to start from scratch each time I run the script, having just 1 row from which I append 'template' boxes where I can put data for the user to copy and paste into emails.
emailsMaxRows = emailsSheet.getMaxRows(); //now that I've deleted, Logger.log() shows that getMaxRows() correctly updates with just 1 row.
emailsSheet.insertRowsBefore(emailsMaxRows, 2);//insert two more, so there are 3 rows in the sheet.
emailsMaxRows = emailsSheet.getMaxRows(); //logger correctly shows 3
templateEmail = spread.getRangeByName("templateEmail"); //email template, A1:E5
templateEmail.copyTo(emailsSheet.getRange(emailsMaxRows,1)); //paste a table of 5 rows to row 3, so there are 7 total.
emailsMaxRows = emailsSheet.getMaxRows(); //should be 7, no?
//NO. Logger shows MaxRow is still 3, how come????? I can visibly see in the sheet there are 7 rows, with formatting and text. I simply don't understand how getMaxRows() doesn't update to return 7.
I need either getMaxRows() or getLastRow() to correctly update each time I delete or insert a row, why can't they do that for me?
SpreadsheetApp.flush() fixed the problem.

How can I enhance my auto hide script in Google Sheets so it wont time out

I have a script that I run on multiple Spreadsheets... it auto hides rows that contain a certain value. Currently this script it setup to run daily around 3:00 am, to ensure no one is active in it while it processes. The issue is I am now running into is these sheets are getting too large to use my current script, which runs line by line. The script times out and doesn't finish. I'm guessing it still runs the script on all the lines that are already hidden.
Here is my current script, which is pretty basic:
function autoHide() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("SHIPPING");
//get data from column
var data = sheet.getRange('AD:AD').getValues();
//iterate over all rows
for(var i=0; i< data.length; i++){
//compare first character, if greater than 0, then hide row
if(data[i][0] > 0){
sheet.hideRows(i+1);
}
}
}
I have tried searching for better options, and found where people were talking about using array filters, or running in batches, just different things that didn't seem to be explained enough for me to translate to what I was working on. I know running this line by line isn't the best way, especially with over a 1,000 rows and growing.
For a best case scenario, I would like to have a very efficient script that uses fractions of the processing my current script does. Otherwise, if there was just a way to run the script on the rows that are visible, that would be almost as good. Worst case scenario, if there is just a way to tell it to pick up where it left off when it gave a time out error... by placing some type of tag or something to know where to start back up.
I don't think linking a sheet is necessary, I just need to be able to hide any row that has a number greater than 0 in column AD, on a sheet called "SHIPPING".
While you could speed up the script itself (for example by batching consecutive rows that need to be hidden) that will also evenctually time out.
Instead, your script should remember in script properties the last row it processed, ao that if the script times out it will continue starting from that row.
You will also need to change the trigger times. Make it run every 10 minutes but only start processing if 1) its past 3am and b) the last row processed is not yet the very last row (which you reset to zero when finished).
this should handle huge sheets just fine. by 5am it would have run 12 times since 3am so it should be able to process 12 times more rows.
Note I chose 10min trigger so that a previous trigger (which could run for 6 minutes) wont ever overlap the next trigger.
Do make sure to set your timezone in the sheet and script file properties so that you use your timezones when checking if its past 3am already.