Row count off by one in google spreadsheet - google-apps-script

I want to delete empty rows. Row 885 is a non-empty row and should be retained. However, my code says (and shows in the msgbox) that 885 is empty and should be deleted. When it gets to i=884, it prints what is really in row 885 and says it will NOT be deleted. So, I'm confused. It appears to be acting upon the data properly, yet it's reporting the row number wrong. I suspect you're going to tell me offsets are zero based and rows start at 1, so if lastrow=888 (1 based), then I need to subtract 1 to match the offset. so what I see as row 885 in the spreadsheet is really row 884 as an offset.
But... when i=row 885 has data, the offset is 884. And when i=884, the offset is 883... so why is it printing what's in row 885??? Seriously confused here. What am I getting wrong?
Last but most importantly... it's deleting the wrong row! How can it be referencing the right data, yet still deleting the wrong row???
var s = SpreadsheetApp.getActiveSheet();
var range = SpreadsheetApp.getActiveSheet().getDataRange();
var i = range.getLastRow();
var msg;
var colNum;
for (; i > 1; i--) { // I like to start at the end
var foundAvalue=0; // reset flag for each row
rowRange = range.offset(i, 0, 1);
var valArray=rowRange.getValues();
var foundAvalue=0;
var msg;
var totalColumns=rowRange.getNumColumns();
colNum=0;
while (colNum < totalColumns && (foundAvalue==0)) {
if (valArray[0][colNum] != '') {
foundAvalue=1;
msg="Row " + i + " =" + valArray[0];
Browser.msgBox(msg);
msg="Row " + i + " will not be deleted";
Browser.msgBox(msg);
}
colNum++;
}
if (foundAvalue == 0) {
msg="Row " + i + " =" + valArray[0] + "WILL be deleted";
Browser.msgBox(msg);
// delete empty row
// s.deleteRow(i);
}
} // end for(i)

Below is some code which should accomplish what you want. You are correct in that the root of the problem is in the 0-based indexing. The tricky part is that JavaScript/Apps Script arrays use 0-based indexing, but when you call something like getLastRow(), the returned range is 1-based. All-in-all, your code is good - it is only that issue that is tripping you up. Hope this helps:
function DeleteFun(){
var mySheet = SpreadsheetApp.getActiveSheet();
var range = mySheet.getDataRange();
// Iterate using a counter, starting at the last row and stopping at
// the header (assumes the header is in the first row)
for (i = mySheet.getLastRow() - 1; i > 0; i--) {
var foundAvalue = 0;
// Here we get the values for the current row and store in an array
rowRange = range.getValues()[i]
// Now we iterate through that array
for (j = 0; j <= rowRange.length; j++) {
// If any non-nulls are found, alert they won't be deleted and move on
if (rowRange[j] != null && rowRange[j] != '') {
foundAvalue = 1;
msg="Row " + (i+1) + " =" + rowRange[0] + " and will not be deleted";
Browser.msgBox(msg);
break;
}
}
if (foundAvalue == 0) {
msg="Row " + (i+1) + " =" + rowRange[0] + "WILL be deleted";
Browser.msgBox(msg);
// Delete empty row
mySheet.deleteRow(i+1);
}
}
}

You could do it in 'pure' array as well if you don't use formulas in your sheet.
Like this for example :
function deleteEmptyRows(){
var sh = SpreadsheetApp.getActiveSheet();
var data = sh.getDataRange().getValues();
var targetData = new Array();
for(n=0;n<data.length;++n){
if(data[n].join().replace(/,/g,'')!=''){ targetData.push(data[n])};// checks the whole row
Logger.log(data[n].join().replace(/,/g,''))
}
sh.getDataRange().clear(); // clear the whole sheet
sh.getRange(1,1,targetData.length,targetData[0].length).setValues(targetData);//write back all non empty rows
}

Related

GoogleScript stopping at random and not completing its given task to sort through a response sheet

So I have this script pulling from a response sheet and running an if statement on each response to check what type of response it is at the current moment I have to types it checks for one being "Register a new Fox" and "Submit a Transaction" I'm pretty new to googlescripts with a decent knowledge of other languages. I'm looking for a solution for why my loop is stopping and how it can be fixed rather, any examples will help a lot. Thanks in advance.
function SetCellData() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var debugSheet = ss.getSheetByName("Debug")
var famTree = ss.getSheetByName("Family")
var transaction = ss.getSheetByName("Transactions")
var sheetMaster = ss.getSheetByName("Form Responses")
debugSheet.getRange("Debug!A1").setValue(sheetMaster.getRange("Form Responses!C2").getValue());
debugSheet.getRange("Debug!A2").setValue(sheetMaster.getRange("Form Responses!C:C").getLastRow());
var mainRange = sheetMaster.getRange("Form Responses!B2:B");
var registeredFoxes = 0
var transactionsMade = 0
for (i=2; i <= sheetMaster.getRange("Form Responses!C:C").getLastRow();i++){
// Checks to see if they are trying to add a new fox.
if (sheetMaster.getRange("Form Responses!C" + i.toString()).getValue() == "Register a New Fox"){
famTree.getRange("Family!B"+(6+registeredFoxes)).setValue(sheetMaster.getRange("Form Responses!D" + i.toString()).getValue());
famTree.getRange("Family!C"+(6+registeredFoxes)).setValue(sheetMaster.getRange("Form Responses!E" + i.toString()).getValue());
famTree.getRange("Family!D"+(6+registeredFoxes)).setValue(sheetMaster.getRange("Form Responses!F" + i.toString()).getValue());
famTree.getRange("Family!E"+(6+registeredFoxes)).setValue(sheetMaster.getRange("Form Responses!G" + i.toString()).getValue());
famTree.getRange("Family!H"+(6+registeredFoxes)).setValue(sheetMaster.getRange("Form Responses!H" + i.toString()).getValue());
registeredFoxes = registeredFoxes + 1;
// Checks to see if they are submiting a transaction.
}else if (sheetMaster.getRange("Form Responses!C" + i.toString()).getValue() == "Submit a Transaction"){
transaction.getRange("Transactions!E"+(8+transactionsMade)).setValue(sheetMaster.getRange("Form Responses!I" + i.toString()).getValue());
transaction.getRange("Transactions!F"+(8+transactionsMade)).setValue(sheetMaster.getRange("Form Responses!L" + i.toString()).getValue());
if (sheetMaster.getRange("Form Responses!J" + i.toString()).getValue() == "Profit"){
transaction.getRange("Transactions!C"+(8+transactionsMade)).setValue("+");
transaction.getRange("Transactions!D"+(8+transactionsMade)).setValue(sheetMaster.getRange("Form Responses!K" + i.toString()).getValue());
}else{
transaction.getRange("Transactions!C"+(8+transactionsMade)).setValue("-");
transaction.getRange("Transactions!D"+(8+transactionsMade)).setValue("-"+ sheetMaster.getRange("Form Responses!K" + i.toString()).getValue());
}
transaction.getRange("Transactions!B"+(8+transactionsMade)).setValue(sheetMaster.getRange("Form Responses!M" + i.toString()).getValue());
transactionsMade = transactionsMade + 1
}else{
debugSheet.getRange("Debug!A3").setValue(registeredFoxes);
}
}
famTree.getRange("Family!B2").setValue("Fox Family Members:\n" + registeredFoxes)
transaction.getRange("Transactions!B4").setValue("Transactions Made:\n" + transactionsMade)
}
The only way that you can speed things up is to get all the data out of all 3 sheet tabs, manipulate the data in the 2D arrays, and then set the new values all at once. Currently you are getting and setting values on every iteration of the loop, so there are read and write operations happening constantly which takes time. You need to read all the data once, and write all the data once.
The loop might be terminating for some reason. And the only reason it might be doing that is if the count hits the last row number, or there is an error in your code:
I would change:
for (i=2; i <= sheetMaster.getRange("Form Responses!C:C").getLastRow();i++){
To:
var L = sheetMaster.getRange("Form Responses!C:C").getLastRow();
Logger.log("L: " + L)
for (i=2; i<=L;i++){
This seems to work but it's not orthodox any adjustments that could be made let me know
Pretty much it was taking to much effort and memory to find the fields that I keep calling so I got all of them at once and then ran statements off that.
function SetCellData() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var debugSheet = ss.getSheetByName("Debug")
var family = ss.getSheetByName("Family")
var transaction = ss.getSheetByName("Transactions")
var sheetMaster = ss.getSheetByName("Form Responses")
// Starts from Row 2 Column C
var vA=sheetMaster.getRange(2, 3,(sheetMaster.getLastRow()-1),(sheetMaster.getLastColumn()-2)).getValues();
var lastColumn = sheetMaster.getLastColumn()
var registeredFoxes = 0
var transactionsMade = 0
var slotsScanned = 0
var L = sheetMaster.getRange("Form Responses!C:C").getLastRow();
Logger.log("L: " + L)
for (i=2; i<=L;i++){
// Checks to see if they are trying to add a new fox.
if (vA[i-2][0] == "Register a New Fox"){
for (j=1;j<=5;j++){
if (j==5){
family.getRange(registeredFoxes+6,j+3).setValue(vA[i-2][j]);
}else{
family.getRange(registeredFoxes+6,j+1).setValue(vA[i-2][j]);
}
}
registeredFoxes ++
// Checks to see if they are submiting a transaction.
}else if (vA[i-2][0] == "Submit a Transaction"){
for (j=7; j<=11;j++){
transaction.getRange(transactionsMade+8,j-5).setValue(vA[i-2][j-1]);
if (vA[i-2][j-1] == "Profit"){
transaction.getRange(transactionsMade+8,j-5).setValue("+");
}else if (vA[i-2][j-1] == "Loss"){
transaction.getRange(transactionsMade+8,j-5).setValue("-");
}else if (j == 9 && vA[i-2][7] == "Loss"){
transaction.getRange(transactionsMade+8,j-5).setValue("-" + vA[i-2][j-1]);
}
}
transactionsMade ++
}else{
Logger.log("Tried to read something...")
}
}
family.getRange(2,2).setValue("Fox Family Members:\n" + registeredFoxes);
transaction.getRange(4,2).setValue("Transactions Made:\n" + transactionsMade);
}

Replace Duplicates with Null Value in Google Script

I've got a list of folder names corresponding to file names within each of these folders.
I'd like to delete the duplicate folder names. This is what I need it to end up as:
My thought was that I would do a reverse loop comparing each entry to the one before it. If it matches add a blank '' to the top of an array. If it doesn't match then add the value of the array entry to the top of the array.
Below is my attempt. For some reason all of the comparisons are false. What am I doing wrong?
//Delete Duplicate folder names
var destRange = summarySht.getRange(startRow + 1,2,detLastRN - startRow,1);
//var folderNamesArr = destRange.getValues();
var folderNamesArr = [['Folder1'],['Folder1'],['Folder2'],['Folder2'],['Folder3'],['Folder4'],['Folder5'],['Folder6'],['Folder6']];
var updatedFolderNamesArr = [];
for (i = folderNamesArr.length - 1; i >= 0; i--) {
if(folderNamesArr[i] != folderNamesArr[i - 1]){
updatedFolderNamesArr.unshift(folderNamesArr[i])
}else{
updatedFolderNamesArr.unshift([''])
}
Logger.log(folderNamesArr[i] + ' vs ' + folderNamesArr[i - 1] + ' = ' + (folderNamesArr[i] === folderNamesArr[i - 1]))
}
Logger.log(updatedFolderNamesArr)
destRange.clearContent();
destRange.setValues(updatedFolderNamesArr);
I don't have many rows. I'm thinking worst case scenario we'll have 100 maybe 200 rows to process.
Update:
I added .toString() and it seems to work. Here is what I did and my issues with it.
//Delete Duplicate folder names
var destRange = summarySht.getRange(startRow + 1,2,detLastRN - startRow,1);
//var folderNamesArr = destRange.getValues();
var folderNamesArr = [['Folder1'],['Folder1'],['Folder2'],['Folder2'],['Folder3'],['Folder4'],['Folder5'],['Folder6'],['Folder6']];
var updatedFolderNamesArr = [];
for (i = folderNamesArr.length - 1; i >= 1; i--) {
if(folderNamesArr[i].toString() !== folderNamesArr[i - 1].toString()){
updatedFolderNamesArr.unshift(folderNamesArr[i])
}else{
updatedFolderNamesArr.unshift([''])
}
Logger.log(folderNamesArr[i] + ' vs ' + folderNamesArr[i - 1] + ' = ' + (folderNamesArr[i] === folderNamesArr[i - 1]))
}
//Add back the first folder
updatedFolderNamesArr.unshift(folderNamesArr[0]);
Logger.log(updatedFolderNamesArr)
destRange.clearContent();
destRange.setValues(updatedFolderNamesArr);
The first issue I had was that folderNamesArr[i - 1] doesn't work when it gets to the last element. So I had to change the limit on the loop to i >= 1. That then caused the issue of there being 1 less element. The first element was missing so I had to add it back to the end.
This seems so hacky to me. Is this acceptable or are there better ways/methods?
How about this answer?
Modification points:
In your script, folderNamesArr is 2 dimensional array which is retrieved with getValue. But, at if(folderNamesArr[i] != folderNamesArr[i - 1]){, the arrays are compared. By this, this if statement is always true. So updatedFolderNamesArr becomes the same with folderNamesArr.
Also, I think that the reason that if(folderNamesArr[i].toString() !== folderNamesArr[i - 1].toString()){ works is this.
But, when if(folderNamesArr[i] != folderNamesArr[i - 1]){ is modified to if(folderNamesArr[i][0] != folderNamesArr[i - 1][0]){, when i is 0, an error occurs.
So, it is required to modify for (i = folderNamesArr.length - 1; i >= 0; i--) { to for (i = folderNamesArr.length - 1; i > 0; i--) {.
But, in this case, because of (i = folderNamesArr.length - 1; i > 0; i--), the result becomes [[""],["Folder2"],[""],["Folder3"],["Folder4"],["Folder5"],["Folder6"],[""]]. The 1st element is not included. In this flow, when i is 1, folderNamesArr[i] is put.
When your script is modified how about the following modification?
Modified script:
var folderNamesArr = [['Folder1'],['Folder1'],['Folder2'],['Folder2'],['Folder3'],['Folder4'],['Folder5'],['Folder6'],['Folder6']];
var updatedFolderNamesArr = [];
for (i = folderNamesArr.length - 1; i > 0; i--) { // Modified
if (folderNamesArr[i][0] != folderNamesArr[i - 1][0]) { // Modified
updatedFolderNamesArr.unshift(folderNamesArr[i]);
} else {
updatedFolderNamesArr.unshift(['']);
}
if (i == 1) updatedFolderNamesArr.unshift(folderNamesArr[i]); // Added
}
Logger.log(updatedFolderNamesArr)
Other pattern:
As other pattern, how about the following script? In this script, updatedFolderNamesArr is flatten and created an array as 1 dimensional array, and then, the array is converted to 2 dimensional array. I think that there are several sample script for your situation.
var folderNamesArr = [['Folder1'],['Folder1'],['Folder2'],['Folder2'],['Folder3'],['Folder4'],['Folder5'],['Folder6'],['Folder6']];
var updatedFolderNamesArr = folderNamesArr
.flat()
.reduce((ar, e) => ar.concat(ar.includes(e) ? '' : e), [])
.map(e => [e]);
Logger.log(updatedFolderNamesArr)
Reference:
getValues()

Improve loading efficiency of App Script code in Google Sheets

ISSUE
I have a spreadsheet whereby I generate the end column based on the other columns present. I do this using the app script code below. There are now 1147 rows in this spreadsheet and I often notice a long period of loading to retrieve all of the rows.
Are there any suggestions on how I can improve the efficiency and responsiveness?
EXAMPLE
ARRAY FORMULA ON END COLUMN
=ARRAYFORMULA(
IF(A2:A="Spec", "# SPEC "&B2:B,
IF(A2:A="Scenario", "## "&B2:B,
IF(A2:A="Step", "* "&TAGS(C2:C,D2:D),
IF(A2:A="Tag", "Tags: "&REGEXREPLACE(B2:B,"\s",""),
IF(A2A="", ""))))))
APP SCRIPT CODE
Utilities.sleep(3000)
/** #OnlyCurrentDoc */
function TAGS(input,textreplacement) {
if (input.length > 0) {
var lst = input.split(",")
var rep = textreplacement.match(/<[^>]*>/g)
for (i in rep){
textreplacement = textreplacement.replace(rep[i],'"'+lst[i]+'"')
}
return textreplacement
}
else{
return textreplacement
}
}
EDIT
From the image below I would like to replace everything with triangle brackets < > in column D, with the values in column C, separated by comma.
I use the Array Formula in column E to do an initial conversion and then use the TAGS function to add in the values.
Ideally I would use the Array Formula in one cell at the top of column E to do all the replacements.
Custom functions in Google Apps Script tend to take long time to process and I wouldn't recommend to use it in several cells. I would like to understand better what you trying to do with this data in order to answer properly, but anyway, I would try one of these two solutions:
1 - Inline formula:
Using only native functions has a better performance. Not sure how you could achieve this, since you are iterating inside that TAGS function.
2- Calculate values interely with Script and replace values in column E:
You could create a function that may run from onEdit event or get activated by a custom menu. Generally it would be like this:
function calculateColumnE() {
var sheet = SpreadsheetApp.openById('some-id').getSheetByName('some-name');
var row_count = sheet.getLastRow();
var input_data = sheet.getRange(1, 1, row_count, 4).getValues();
var data = [];
for (var i = 0; i < row_count; i++) {
var row_data; // this variable will receive value for column E in this row
/*
...
manage input_data here
...
*/
data.push([row_data]); // data array MUST be a 2 dimensional array
}
sheet.getRange(1, 5, data.length, 1).setValues(data);
}
EDIT
Here is the full code for solution 2:
function TAGS(input,textreplacement) { //keeping your original function
if (input.length > 0) {
var lst = input.split(",")
var rep = textreplacement.match(/<[^>]*>/g)
for (i in rep){
textreplacement = textreplacement.replace(rep[i],'"'+lst[i]+'"')
}
return textreplacement
}
else{
return textreplacement
}
}
function calculateColumnE() {
var sheet = SpreadsheetApp.openById('some-id').getSheetByName('some-name');
var row_count = sheet.getLastRow();
var input_data = sheet.getRange(1, 1, row_count, 4).getValues();
var data = [];
for (var i = 0; i < row_count; i++) {
var row_data; // this variable will receive value for column E in this row
if (input_data[i][0] == "Spec") {
row_data = "# SPEC " + input_data[i][1];
} else if (input_data[i][0] == "Scenario") {
row_data = "## " + input_data[i][1];
} else if (input_data[i][0] == "Step") {
row_data = "* " + TAGS(input_data[i][2], input_data[i][3]);
} else if (input_data[i][0] == "Tag") {
row_data = "Tags: " + input_data[i][1].replace(/\s/, ''); // not sure what this is doing
} else if (input_data[i][0] == "") {
row_data = "";
}
data.push([row_data]); // data array MUST be a 2 dimensional array
}
sheet.getRange(1, 5, data.length, 1).setValues(data);
}
I also created a working example, which you can check here: https://docs.google.com/spreadsheets/d/1q2SYD7nYubSuvkMOKQAFuGsrGzrMElzZNIFb8PjM7Yk/edit#gid=0 (send me request if you need it).
It works like a charm using onEdit event to trigger calculateColumnE() with few lines, I'm curious to know about the result in your 1000+ rows sheet. If it get slow, you may need to run this function manually.
Not sure if this will be faster:
function TAGS(input,tr) {
if (input.length > 0) {
var lst = input.split(",");
var i=0;
tr=tr.replace(/<[^>]*>/g,function(){return '"' + lst[i++] + '"';});
}
return tr;
}

Speed up changes from script to scattered cells in google sheets

I have a sheet that shows the results of calculations based on other backing sheets. Changes are made manually, but the "summary" sheet is only formulae. These changes appear in scattered cells, non-contiguous for the most part.
I want to highlight which cells have changed in the summary sheet after a manual change in the backing sheets. For that, I'm using a second summary sheet which starts as a copy of the main one.
The final ingredient is a script that runs after edits. It traverses the summary range and compares values to the second copy. Any differences get highlighted in the main summary and copied back to the second summary.
This process does work but is quite slow, I think due to the updates. Pseudo-code:
var src = summary.getRange(...)
var dst = copy.getRange(...)
var src_cell;
var dst_cell;
src.setBackground('white'); // Bulk reset of changes
for (row = 1; row < src.getNumRows(); row++) {
for (col = 1; col < src.getNumColumns(); col++) {
src_cell = src.getCell(row, col);
dst_cell = src.getCell(row, col);
if (src_cell.getDisplayValue() != dst_cell.getDisplayValue()) {
dst_cell.setValue(src_cell.getDisplayValue());
src_cell.setBackground('gray');
}
}
}
I think there is no way to bulk-update scattered ranges, which seems a straightforward solution.
I'm looking for ways to speed up this process, either in the script or by using some other strategy.
Per official "best practices," you should batch-read associated cell data rather than repeatedly read and possibly write values. This statement does assume that setting values in dst does not influence values for future reads.
Thus, the simplest change is to use Range#getDisplayValues on src and dst:
...
src.setBackground("white");
var srcValues = src.getDisplayValues();
var dstValues = dst.getDisplayValues();
srcValues.forEach(function (srcRow, r) {
var dstRow = dstValues[r];
srcRow.forEach(function (value, c) {
if (value !== dstRow[c]) {
dst.getCell(r + 1, c + 1).setValue(value);
src.getCell(r + 1, c + 1).setBackground("gray");
}
});
});
An additional optimization is to use the RangeList class to batch the changes. To create a RangeList, you need an array of cell / range notations, which can use R1C1- or A1-style addressing. R1C1 is simplest to compute.
...
var dstChanges = [];
var srcChanges = [];
...
if (value !== dstRow[c]) {
dstChanges.push({row: r + 1, col: c + 1, newValue: value});
srcChanges.push({row: r + 1, col: c + 1});
}
...
if (srcChanges.length > 0) {
var srcRow = src.getRow();
var dstRow = dst.getRow();
var srcCol = src.getColumn();
var dstCol = dst.getColumn();
copy.getRangeList(dstChanges.map(function (obj) {
return "R" + (obj.row + dstRow) + "C" + (obj.col + dstCol);
}).getRanges().forEach(function (rg, i) {
rg.setValue(dstChanges[i].newValue);
});
summary.getRangeList(srcChanges.map(function (obj) {
return "R" + (obj.row + srcRow) + "C" + (obj.col + srcCol);
}).setBackground("gray");
}
...
Other Refs
Array#forEach
Array#map
Array#push

#REF! being added to formula by setFormula() instead of an actual reference: IF(NOT(ISBLANK(N3)),#REF!,0))

I've created a script that generates markbooks for teachers in a school.
The script adds formulas that convert 'raw marks' into individual test grades.
Additionally, an overview of how a pupil is doing over all the tests they have sat so far is calculated from a formula looks up the percentage to assign an overall grade.
The part of the script I've written to accomplish this is below.
The issue I am having is that although the formula is being constructed correctly, some of the cell references are not being added. Instead of the cell ref I'm just getting #Ref!
In the case of the example sheet below, the 3 #ref! should actually be O1, R1 and U1.
I've separated this part out in my script as a variable (totalRef) on it's own to try to help me debug.
My first thought was that this was because the formula was being added and ins some cases the column that was being referenced did not yet exist, but I've eliminated that and the problem still persists.
Example sheet:
https://docs.google.com/spreadsheets/d/1QXDinhu6Ywlf0lNe3dZBLWMf0pxPrKHgTqfPeEfn7ug/edit?usp=sharing
var noOfAssessments = assessments.length;
var currentCol = 14;//first raw mark assessment column
var startCumul= "";
var endCumul = "";
for (var no = 0; no < noOfAssessments; no ++)
{
var totalRef = columnToLetter(currentCol+1)+"1";
Logger.log(years[y] + " TOTALREF IS "+totalRef);
startCumul = startCumul + "(IF(NOT (ISBLANK("+columnToLetter(currentCol)+(fRow +1)+")),"+columnToLetter(currentCol)+(fRow +1)+",0))";
endCumul = endCumul + "(IF(NOT(ISBLANK("+columnToLetter(currentCol)+(fRow +1)+")),"+totalRef+",0))"
if (no < noOfAssessments - 1)
{
startCumul = startCumul + "+";
endCumul = endCumul + "+";
}
currentCol = currentCol + 3;//3 IS THE NUMBER OF COLS BETWEEN ASSESSMENTS. THIS VAL MIGHT NEED TO BE CHANGE IF USING QLA
}
var wholeCumulFormula = "=IFERROR(LOOKUP(ROUND((" + startCumul + ")/("+endCumul+")*"+getYearPercentages(years[y],setupData)+"),{";
//add the base percentage boundaries
var pBoundaries = getPercentageBoundaries(setupData);
wholeCumulFormula = wholeCumulFormula + pBoundaries + "},{\"1C\",\"1B\",\"1A\",\"2C\",\"2B\",\"2A\",\"3C\",\"3B\",\"3A\",\"4C\",\"4B\",\"4A\",\"5C\",\"5B\",\"5A\",\"6C\",\"6B\",\"6A\",\"7C\",\"7B\",\"7A\",\"8C\",\"8B\",\"8A\",\"9C\",\"9B\",\"9A\"}),\"U\")";
if (!yearData[fRow][12])//only overwrite blank cells
{
yearSheet.getRange(fRow+1,13,1,1).setFormula(wholeCumulFormula);
}
This is the function used to create the ref:
function columnToLetter(column)
{
var temp, letter = '';
while (column > 0)
{
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}