sheet.appendrow in adwords scripts is appending too many lines - google-apps-script

I'm trying to add rows to a google spreadsheet through an adwords script which runs daily.
Some of the code so far is:
var report_iter = AdWordsApp.report(
'SELECT ' + columns_str +
'FROM ACCOUNT_PERFORMANCE_REPORT ' +
'DURING YESTERDAY', { apiVersion: 'v201302' }
).rows();
while(report_iter.hasNext()) {
var row = report_iter.next();
var row_array = [""]; // This is deliberate to include an empty cell in column A in the spreadsheet.
for(var i in columns) {
row_array.push(row[columns[i]]);
}
sheet.appendRow(row_array); // I think this line might be the problem
}
It is working properly, however, it has some unwanted outcomes as well. What I want to stop is every time I append a row to the bottom of the spreadsheet, it also appends 50 other rows of blank cells. Then, the next time I append a row, it appends it to the bottom of the spreadsheet, 50 rows after the one before... Is there any way to stop this. I had a look at using feed lists but I don't know how to do that or if you can with adwords scripts.
The only other thing I was thinking was to insert a new row to the bottom every time, find the row number, then insert data based on the row number.
What I had was working perfectly 3 days ago, but now it has just stopped.
Any suggestions or ideas?

1) you should not use appendRow. It would be much faster and efficient if you write all the rows at once using getRange setValues and using sheet.getLastRow to calculate the starting row.
2) if you do use appendRow, it will append to the last non-empty row, so it should work ok and shouldnt be writting past the blank rows that are autoinserted. However maybe you have something like an arrayformula or something else operating on an entire column that is writting in those empty rows at the bottom. Even if it writes blank values those rows wont be considered empty so the next appendRow will write below them.

The something is probably one of the calls setting the array to 30 rows, then only emptying the array, but not creating a new resized one.
But #1 is the way to go...

Related

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

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.

I need to check a specific range to find the last cell that has values, then paste values on the next cell down

I am looking for assistance on a function. I want to check a specific range(F12:F34), find the last cell in that range that has values, then paste another set of values (already copied and waiting) in the next cell below. Basically this will fill this range throughout the day. Each row in the range is a duration at a job site. F column is the stop time of the assigned job. I was able to do this with a lot of IF's and Else IF's but I was hoping there would be a much more elegant (and shorter) code that could be written. Hope this explains what I'm trying accomplish.
Here is a link the sheet to see what it looks like:
https://docs.google.com/spreadsheets/d/188fYdI50uJ-3Qpy-GzP4l0xgoJlgm_QegWmVCgarU-Y/edit?usp=sharing
You can get last row by reading all the values in column and then looping for blank in the cells. If you think that you can get blanks inbetween then you will have loop in reverse order. ie: for(var i=rangeValues.length-1;i>=0;i--). Well, you may also come across situations where last row of spreadsheet will be filled with some data. In such cases inorder for the program to continue running you can insert a row and then return its position.
function nextFreeRow(){
var rangeValues = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('foo').getRange('F:F').getValues();
for(var i=0;i<rangeValues.length;i++){
if(rangeValues[i][0].toString() == "") return i+1;
}
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('foo').insertRowAfter(rangeValues.length);
return rangeValues.length+1;
}

Copying values from Google sheet, add time stamp, prevent duplicates

I'm using Kimono to scrape a site that lists active development permits. For a one off data scrape it's fine, the problem is that there is no way of sorting new data. Every time Kimono scrapes it updates the entire array.
This is what the sheet currently looks like
https://docs.google.com/spreadsheets/d/1BH8ESAHQJrog6x8nRBOpgBN-nTN1_aDY7wr8W_YYet0/edit#gid=1865015934
The first sheet is automatically populated and overwritten by Kimono. It seems like the most logical way of making this work would be to copy the values to another sheet, adding a time stamp when this happens and then preventing duplicate values from being posted.
Following this thread is was able to muster this code
I've got the copying part down with the following:
function moveValuesOnly() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getRange('Building Permits!A1:D');
source.copyTo(ss.getRange('Sheet2!A1'), {contentsOnly: true});
source.clear();
}
What I am trying to figure out is how to prevent duplicates based on the URL value.
I know that it is right in front of me, but I'm drawing a blank on how to get this to work.
This Google documentation article on removing duplicates is very well written, so I won't duplicate it: https://developers.google.com/apps-script/articles/removing_duplicates
It has exactly what you need. Read the later part of the article where it talks about how to check duplicates not for the entire row, but specific columns in that row. Once you understand that, your problem is straightforward to solve. Just use 2 arrays, to hold the contents of the rows from the 2 sheets as in the example they've given. compare the first column value of the current row. if it matches, don't copy the row over.
note: this works only when you copy row-by-row into the target sheet, not the entire range as your'e doing right now. But that's unavoidable.

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.