Been dealing with this code for a couple of days now. What I want to achieve in the spreadsheet is, move some columns of a single row to another row based on some criteria, and leave the older rows clear of content.
To put it other way, I check at which row columns B, C and H (let's call this row N° '3') match exactly with values at columns B, C and N (let's call this row N° '9'). Then I need to move information from J9:N9 to J3:N3.
So this is the code I've come up with. Problem is, at the end no changes are being made to the spreadsheet nor the 'rows' array when I log it.
function reordenarFacturas(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('AP que esperan');
var lastRow = sheet.getLastRow(); //get the last Row with values
var filteredArray = [];
const rows = sheet.getDataRange().getValues(); // get data as an array
const nombresAP = sheet.getSheetValues(3,2,lastRow-2,2); //get columns 2 and 3 as an array
//Clean nombresAP array from special characters so that later filterLogic works correctly (not using filter anymore, but still wanted to clear names list).
for (h=0; h<lastRow-2; h++){ //rows
for(j=0; j<2;j++){ //columns
nombresAP[h][j] = nombresAP[h][j].toString().toUpperCase();
nombresAP[h][j] = nombresAP[h][j].toString().replace(/á/ig, "A");
nombresAP[h][j] = nombresAP[h][j].toString().replace(/é/ig, "E");
nombresAP[h][j] = nombresAP[h][j].toString().replace(/í/ig, "I");
nombresAP[h][j] = nombresAP[h][j].toString().replace(/ó/ig, "O");
nombresAP[h][j] = nombresAP[h][j].toString().replace(/ú/ig, "U");
nombresAP[h][j] = nombresAP[h][j].toString().replace(" ", " ");
}
}
sheet.getRange("B3:C" + lastRow).setValues(nombresAP);
//End of the cleaning section
//NOT IN USE CURRENTLY, SAVED JUST IN CASE
//Filterlogic works as a way to input several parameters to the filter method used below (now erased).
/*
var filterLogic = function(item){ //function to pass filter data later
if (item[1] === apellidoAP && item[2] === nombreAP && item[7] === periodoFacturado){
return true;
}
else{
return false;
}
}
*/
//Here I check whether certain columns have different values. If they do, then I retrieve some values on variables and push them to an array for later use.
for (var h = 2; h<lastRow; h++){ // h starts at 2 because data starts at row N° 3
if (rows[h][9] == '') continue; // If cells on this columns are empty, I don't need the row and can evaluate the next
else if (rows[h][7] != rows[h][13]){ // If this two different cells of the same row have different values, I then
//assign some data to variables, for later filtering other rows with said data
var apellidoAP = rows[h][1];
var nombreAP = rows[h][2];
var periodoInformado = rows[h][7];
var factura = rows[h][9];
var fechaFactura = rows[h][10];
var nroFactura = rows[h][11];
var montoFactura = rows[h][12];
var periodoFacturado = rows[h][13];
filteredArray.push([[apellidoAP], [nombreAP], [periodoFacturado], [factura], [fechaFactura], [nroFactura], [montoFactura], [periodoFacturado]]);
// Here I'm 'cleaning' some fields in the rows where I've found the differences (doesn't seem to be working)
rows[h][9] == '';
rows[h][10] == '';
rows[h][11] == '';
rows[h][12] == '';
rows[h][13] == '';
}
}
//And finally here, I check whether some filteredArray values match some values in the 'rows' array. If they do match, then I replace some more values in rows array with values from filteredArray. This doesn't seem to be working either.
for (p = 2; p<rows.length; p++){
for (r = 0; r<filteredArray.length; r++){
if (rows[p][1] == filteredArray [r][0] && rows[p][2] == filteredArray[r][1] && rows[p][7] == filteredArray[r][2]){
rows[p][9] == filteredArray[r][3];
rows[p][10] == filteredArray[r][4];
rows[p][11] == filteredArray[r][5];
rows[p][12] == filteredArray[r][6];
rows[p][13] == filteredArray[r][7];
}
}
}
//Finally I set rows back at the spreadsheet with all changes made, but no changes are shown in the spreadsheet once the script ends running.
sheet.getRange("A1:U" + lastRow).setValues(rows);
}
```
Related
I've been jogging my brain trying to figure out how to write this script to transpose data from one sheet to another from a pretty dirty sheet.
There are other questions like this but none seem to be like my particular use case.
This is how the sheet is currently structured (somewhat):
The biggest issue here is I have no concrete idea how many rows a particular group of data will be, But I know there are always a bunch of blank rows between each group of data.
I found a script that took me half way:
function myFunction() {
//Get values of all nonEmpty cells
var ss = SpreadsheetApp.getActiveSheet();
var values = ss.getRange("D:D").getValues().filter(String);
//Create object with 3 columns max
var pasteValues = [];
var row = ["","",""];
for (i = 1; i<values.length+1; i++){
row.splice((i%3)-1,1,values[i-1]);
if(i%3 == 0){
pasteValues.push(row);
var row = ["","",""]
}
}
if(row != []){
pasteValues.push(row)
}
//Paste the object in columns A to C
ss.getRange(1,1,pasteValues.length,pasteValues[0].length).setValues(pasteValues);
}
But in that case the asker dataset was fixed. I can loosely say that the max number of rows each group would have is 10(this is an assumption after browsing 3000 rows of the sheet...but if the script can know this automatically then it would be more dynamic). So with that in mind...and after butchering the script...I came up with this...which in no way works how it should currently(not all the data is being copied):
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyfrom')
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyto')
var values = copyfrom.getRange("A:A").getValues().filter(Array);
var pasteValues = [];
var row = [];
for (i = 1; i<values.length; i++){
if(values[i] != ""){
row.push(values[i])
}
Logger.log(row);
if(i%10 == 0){
pasteValues.push(row);
row = []
}
}
if(row != []){
pasteValues.push(row)
}
copyto.getRange(1,1,pasteValues.length,pasteValues[0].length).setValues(pasteValues);
}
I'm pretty sure I should maybe still be using array.splice() but haven't been successful trying to implement it achieve what i want, here's how the transposed sheet should look:
Info:
Each group of addresses inside the "copyfrom" sheet would be separated by at least 1 blank line
The length of an address group is not static, some can have 5 rows, others can have 8, but address groups are always separated by blank rows
Any help is appreciated
You are right to iterate all input values, and I can suggest the similar code:
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyfrom')
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyto')
var values = copyfrom.getRange("A:A").getValues();
var pasteValues = [[]]; // keep result data here
values.forEach(function(v) { // Iterate all input values
// The last row to be filled in currently
var row = pasteValues[pasteValues.length - 1];
if (v[0]) {
row.push(v[0]);
} else if (row.length > 0) {
while (row.length < 10) {
row.push(''); // Adjust row length
}
pasteValues.push([]);
}
});
if (pasteValues[pasteValues.length - 1].length == 0) pasteValues.pop();
copyto.getRange(1, 1, pasteValues.length, pasteValues[0].length).setValues(pasteValues);
}
Solution:
Assuming that every new row begins with Name, you can use this script to rearrange the column:
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyFrom');
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyTo');
var lastRow = copyfrom.getLastRow();
var values = copyfrom.getRange(1,1,lastRow).getValues().filter(Array);
var pasteValues = [];
var row = [];
var maxLen = 1;
// rearrange rows
for (i = 0; i < values.length; i++) {
if (values[i] == "Name" && i > 0) {
pasteValues.push(row);
row = [values[i]];
}
else if (values[i] != "") {
row.push(values[i]);
if (row.length > maxLen) {
maxLen = row.length;
}
}
}
pasteValues.push(row);
// append spaces to make the row lengths the same
for (j = 0; j < pasteValues.length; j++) {
while (pasteValues[j].length < maxLen) {
pasteValues[j].push('');
}
}
copyto.getRange(1,1,pasteValues.length,maxLen).setValues(pasteValues);
}
Sample I/O:
As far as I can tell, there is no way to get the columns to line up in the output since you don't have any way to tell the difference between, for example, an "address 2" and a "City".
However, as far as merely grouping and transposing each address. This one formula, in one cell, in the tab here called MK.Help will work from the data you provided. It will work for as many contacts as you have.
=ARRAYFORMULA(QUERY(QUERY({A:A,IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),COUNTIFS(IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),A:A,"<>",ROW(A:A),"<="&ROW(A:A))},"select MAX(Col1) where Col1<>'' group by Col2 pivot Col3",0),"offset 1",0))
Follow up to Sending duplicate data to array, then checking if said array is empty or not to decide what code to run next, if statement not working correctly.
I'm pretty much trying to copy the conditional formatting that I put to notify users of "bad" data, but with script to prevent sending bad data and giving unique error messages. With the answer from my last question, the checks and preventing sending no data or data with duplicates works, but I'm now stuck on preventing sending of data that does not match the format of a four digit number.
My conditional formatting was to set the background color to orange on anything that was not equal to or in between 1000 and 9999, but I'm having trouble getting the scripting to work--I'm trying to use negation of anything in between those two values as the "false" that will prompt an error message, and the "true" to let the rest of the script run that will send the data and notification emails out. However, this makes it say that there are bad values even if I do have the correct data in. Removing the negation lets anything go through, like it's not actually checking it. Any ideas?
The section I'm asking about is the last else if statement:
else if (!data.every(function(num) {return num >= 1000 && num <= 9999})) {
SpreadsheetApp.getUi().alert("You have incorrectly formatted tallies, tallies must be four digits.", SpreadsheetApp.getUi().ButtonSet.OK);
}
Total code is below.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssSheet = ss.getActiveSheet();
//https://techyesplease.com/code/google-apps-script-find-duplicates-sheets/
function readData() {
var dataColumn = 3;
var firstRow = 6;
var lastRow = ssSheet.getLastRow();
var numRows = lastRow - firstRow + 1;
var columnRange = ssSheet.getRange(firstRow, dataColumn, numRows);
//var rangeArray = columnRange.getValues();
// Convert to one dimensional array
//rangeArray = [].concat.apply([], rangeArray);
var rangeArray = columnRange.getValues().flat().filter(String);
return rangeArray;
}
// Sort data and find duplicates
function findDuplicates(dataAll) {
var sortedData = dataAll.slice().sort();
var duplicates = [];
for (var i = 0; i < sortedData.length - 1; i++) {
if (sortedData[i + 1] == sortedData[i]) {
duplicates.push(sortedData[i]);
}
}
return duplicates;
}
//Use the same string for this variable as the name for the requestor for his or her column of data. E.g., John Doe
//****GLOBALS****
var targetSheet = 'All Tallies'; //replace with sheet/tab name
var targetSpreadsheetID = 'id' //replace with destination ID
var targetURL = 'url'
//var dataNotificationReceivingEmailAddresses =
//Set up to be able to easily change what emails the data notification goes to?
function sendDataAndTimestamp2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssSheet = ss.getActiveSheet();
var sourceRange = ssSheet.getRange('C6:C401');
//assign the range you want to copy, could make C6:C? No, would like dynamic.
var data = sourceRange.getValues();
var nameRange = ssSheet.getRange('C4:D4');
var nameValue = nameRange.getDisplayValue();
var tallyDateRange = ssSheet.getRange('C2');
var tallyDateValue = tallyDateRange.getDisplayValue();
var tallyDateText = 'Tallies to run on '+ tallyDateValue;
var tallyAmountRange = ssSheet.getRange(8,1);
var tallyAmount = tallyAmountRange.getDisplayValue();
var tallyAmountNumberOnly = data.filter(String).length;
//Used as tallyAmount includes text, for some cases need the number only
var thisDocumentUrl = ss.getUrl();
//Variables for the sending/source spreadsheet above
//Initial confirmation alert, need checks for blank or error tallies first. First condition needs to loop through data variable and check that if any values are numbers not between 1000 and 9999, throw up Ui alert error message. Second condition goes to result variable?
//Reference the earlier functions
var dataArray = readData();
var duplicates = findDuplicates(dataArray);
//Need to check data and have error message if duplicates.length >=1, if 0 allow, refuse if data length less than 1
if (duplicates.length !== 0) {
SpreadsheetApp.getUi().alert("Your tallies include duplicates, please remove them then try again.", SpreadsheetApp.getUi().ButtonSet.OK);
Logger.log(duplicates);
}
else if (dataArray.length ===0) {
SpreadsheetApp.getUi().alert("You have not input any tallies.", SpreadsheetApp.getUi().ButtonSet.OK);
}
else if (!data.every(function(num) {return num >= 1000 && num <= 9999})) {
SpreadsheetApp.getUi().alert("You have incorrectly formatted tallies, tallies must be four digits.", SpreadsheetApp.getUi().ButtonSet.OK);
}
/*https://www.youtube.com/watch?v=gaC290XzPX4&list=PLv9Pf9aNgemvD9NFa86_udt-NWh37efmD&index=10
Every method
var arr = [1,2,3,4];
var allTalliesGood = data.every(function(num){
return num < 9
});
//Use with num >= 1000, num <=9999, and then if true continue, if false stop and give error msg
*/
/*
dataFiltered = data.filter(filterlogic);
var filterlogic = function(tally){
if (tally >= 1000 && tally <= 9999){
return true;
} else {
return false
}
}
//if use false for good tallies, and rename dataFiltered to something like "dataBad", then check if dataBad has true matches in it...
//need to check for strings? what happens if letters/symbols in tally range?
//var dataBad = data.filter(function(tally){ return tally < 1000 || tally > 9999;});
// use OR (||) for getting tallies great than or less than, what about letters/symbols? Use NOT good range?
//https://www.youtube.com/watch?v=hPCIOohF0Fg&list=PLv9Pf9aNgemvD9NFa86_udt-NWh37efmD&index=8
//sort method link above
*/
else {
// rest of code
var result = SpreadsheetApp.getUi().alert("You're about to notify scheduler of the following number of tallies: " + tallyAmountNumberOnly, SpreadsheetApp.getUi().ButtonSet.OK_CANCEL);
if(result === SpreadsheetApp.getUi().Button.OK) {
//Code to send out emails and data
As far as I can tell in the function sendDataAndTimestamp2() you need to get rid of empty values from data. It can be done with filter(String) method:
var data = sourceRange.getValues().filter(String); // <-- here
. . .
else if (!data.every(function(num) {return num >= 1000 && num <= 9999}))
. . .
Or, I don't know, perhaps you meant dataArray instead of data:
else if (!dataArray.every(function(num) {return num >= 1000 && num <= 9999}))
Btw, the line can be shortened a bit:
else if (!data.every(x => (x >= 1000) && (x <= 9999)))
There are two ways that i am able to add an auto increment column. By auto-increment, i mean that if column B has a value, column A will be incremented by a numeric value that increments based on the previous rows value.
The first way of doing this is simple, which is to paste a formula like the one below in my first column:
=IF(ISBLANK(B1),,IF(ISNUMBER(A1),A1,0)+1)
The second way i have done this is via a GA script. What i found however is performance using a GA script is much slower and error prone. For example if i pasted values quickly in the cells b1 to b10 in that order, it will at times reset the count and start at 1 again for some rows. This is because the values for the previous rows have not yet been calculated. I assume that this is because the GA scripts are probably run asynchronously and in parallel. My question is..is there a way to make sure each time a change happens, the execution of this script is queued and executed in order?
OR, is there a way i should write this script to optimize it?
function auto_increment_col() {
ID_COL = 1;
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
//only increment column 1 for sheets in this list
var auto_inc_sheets = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("auto_inc_sheets").getValues();
auto_inc_sheets = auto_inc_sheets.map(function(row) {return row[0];});
var is_auto_inc_sheet = auto_inc_sheets.indexOf(spreadsheet.getSheetName()) != -1;
if (!is_auto_inc_sheet) return;
var worksheet = spreadsheet.getActiveSheet();
var last_row = worksheet.getLastRow();
var last_inc_val = worksheet.getRange(last_row, ID_COL).getValue();
//if auto_inc column is blank and the column next to auto_inc column (col B) is not blank, then assume its a new row and increment col A
var is_new_row = last_inc_val == "" && worksheet.getRange(last_row, ID_COL+1).getValue() != "";
Logger.log("new_row:" + is_new_row + ", last_inc_val:" + last_inc_val );
if (is_new_row) {
var prev_inc_val = worksheet.getRange(last_row-1, ID_COL).getValue();
worksheet.getRange(last_row, ID_COL).setValue(prev_inc_val+1);
}
}
There is my vision of auto increment https://github.com/contributorpw/google-apps-script-snippets/tree/master/snippets/spreadsheet_autoincrement
The main function of this is
/**
*
* #param {GoogleAppsScript.Spreadsheet.Sheet} sheet
*/
function autoincrement_(sheet) {
var data = sheet.getDataRange().getValues();
if (data.length < 2) return;
var indexCol = data[0].indexOf('autoincrement');
if (indexCol < 0) return;
var increment = data.map(function(row) {
return row[indexCol];
});
var lastIncrement = Math.max.apply(
null,
increment.filter(function(e) {
return isNumeric(e);
})
);
lastIncrement = isNumeric(lastIncrement) ? lastIncrement : 0;
var newIncrement = data
.map(function(row) {
if (row[indexCol] !== '') return [row[indexCol]];
if (row.join('').length > 0) return [++lastIncrement];
return [''];
})
.slice(1);
sheet.getRange(2, indexCol + 1, newIncrement.length).setValues(newIncrement);
}
But you have to open the snippet for details because this doesn't work without locks.
I'm working on a script to encode data in fields for insertion into HTML (the cells contain words like Planétarium and Rua João Bettega, 01). I am getting an error and don't know what to do with it. The error says "Cannot convert Array to Object[][]"
I'm definitely passing a two-dim array to .setValues(), so I know that's not it (found that to be a common problem). Any ideas? Here's my function:
// columns[] and columns2encode[] are set in the scope above this function
function _encodeData() {
var sheet = _getSheet();
var data = sheet.getDataRange().getValues();
var rowCnt = data.length;
var colCnt = data[0].length;
var data2set = new Array(rowCnt-1); // creates array w/ row.length indeces
var colEncodeIndexList = [];
var origVal, encodedVal, range, cell; // used later in for loop(s)
toast('Start Encoding Data');
// loop every row
for(var ri = 0; ri < data.length; ri++) {
if(ri !== 0) {
data2set[ri] = new Array(colCnt); // creates array w/ length of ~ 29
}
// loop every cell (column entry in the given row)
for(var ci = 0; ci < data[ri].length; ci++) {
// get text content of current cell
origVal = data[ri][ci];
// if first row, headers - save matches in array for later
if(ri === 0) { // I split the ifs so inArray isn't called EVERY time
if(inArray(columns2encode, origVal)) { // add col header if "on the list"
colEncodeIndexList.push(ci);
}
} else { // isn't the header row
// if ! first row, values - col num must be in saved headers to encode OR it's a number
if(!inArray(colEncodeIndexList, ci) || typeof origVal === 'number') {
// just use origVal
data2set[ri][ci] = origVal;
} else {
// use encodedVal
encodedVal = _htmlEncode(origVal);
data2set[ri][ci] = encodedVal;
}
} // else
} // for (cells/cols)
} // for (rows)
// Now pass array of arrays (data) to setValues()
var setRange = sheet.getRange(2, 1, (data.length-1), data[0].length);//.getA1Notation(); // starts at 2, 1 (row 2, col 1) to ignore column header
setRange.setValues(data2set);
toast('Finished Encoding Data');
}
Thanks in advance!
The problem is that data2set[0] is never initialized (it is always NULL).
The easiest way to fix the problem is to remove the data2set[0] before outputting to the spreadsheet:
data2set.shift(); // remove 1st item
setRange.setValues(data2set);
I have 2 spreadsheet in my Drive. I want to pull data from a cell in 1 spreadsheet and copy it in another.
The spreadsheet "TestUsage" will sometimes have data in column A, but none is column B.
I would like so that when I open the spreadsheet, it would populate that empty cell in sheet "TestUsage" from sheet "TestParts".
Here is my code:
var ssTestUsage = SpreadsheetApp.getActiveSpreadsheet();
var sTestUsage = ssTestUsage.getActiveSheet();
var lastRowTestUsage = sTestUsage.getLastRow();
var rangeTestUsage = sTestUsage.getSheetValues(1, 1, lastRowTestUsage, 4);
var TESTPARTS_ID = "1NjaFo0Y_MR2uvwit1WuNeRfc7JCOyukaKZhuraWNmKo";
var ssTestParts = SpreadsheetApp.openById(TESTPARTS_ID);
var sTestParts = ssTestParts.getSheets()[0];
var lastRowTestParts = sTestParts.getLastRow();
var rangeTestParts = sTestParts.getSheetValues(1, 1, lastRowTestParts, 3);
function onOpen() {
for (i = 2; i < lastRowTestUsage; i++) {
if (rangeTestUsage[i][0] !== "" && rangeTestUsage[i][1] == "") {
for (j = 1; j < lastRowTestParts; j++) {
if (rangeTestUsage[i][0] == rangeTestParts[j][0]) {
Logger.log(rangeTestUsage[i][1]);
Logger.log(rangeTestParts[j][1]);
rangeTestUsage[i][1] = rangeTestParts[j][1];
break;
}
}
}
}
}
The problem with this is this doesn't do anything:
rangeTestUsage[i][1] = rangeTestParts[j][1];
I know there must be a method that can get data from one range to another.
Please let me know if I am totally incorrect or I am on the right path.
the statement
"this doesn't do anything:"
rangeTestUsage[i][1] = rangeTestParts[j][2];
is not really true... and not really false neither..., actually it does assign the value to rangeTestUsagei but you dont see it because it is not reflected in the spreadsheet.
Both values are taken from the Sheet using getValues so at that time they are both array elements.
What is missing is just writing back the array to the sheet using the symetrical statement setValues()
Give it a try and don't hesitate to come back if something goes wrong.
EDIT :
I didn't notice at first that you were using getSheetValues instead of getValues (simply because I never use this one)... the only difference is that getValues is a method of the range class while yours belongs to the sheet class; the syntax is similar in a way, just use
Sheet.getRange(row,col,width,height).getValues()
it takes one word more but has the advantage to have a direct equivalent to set values
Sheet.getRange(row,col,width,height).setValues()
Serge insas has a good explanation of why your code doesn't work and hints at the solution below.
I recommend you use an array to store the updated values of column B that you want then set the entire column at the end.
Modifying your code...
var ssTestUsage = SpreadsheetApp.getActiveSpreadsheet();
var sTestUsage = ssTestUsage.getActiveSheet();
var lastRowTestUsage = sTestUsage.getLastRow();
var rangeTestUsage = sTestUsage.getSheetValues(1, 1, lastRowTestUsage, 2);
var TESTPARTS_ID = "1NjaFo0Y_MR2uvwit1WuNeRfc7JCOyukaKZhuraWNmKo";
var ssTestParts = SpreadsheetApp.openById(TESTPARTS_ID);
var sTestParts = ssTestParts.getSheets()[0];
var lastRowTestParts = sTestParts.getLastRow();
var rangeTestParts = sTestParts.getSheetValues(1, 1, lastRowTestParts, 2);
var colB = [];
function onOpen() {
for (i = 2; i < lastRowTestUsage; i++) {
if (rangeTestUsage[i][0] !== "" && rangeTestUsage[i][1] == "") {
var matched = false;
for (j = 1; j < lastRowTestParts; j++) {
if (rangeTestUsage[i][0] == rangeTestParts[j][0]) {
//Logger.log(rangeTestUsage[i][1]);
//Logger.log(rangeTestParts[j][1]);
colB.push([rangeTestParts[j][1]]); // push the value we want into colB array
matched = true;
break;
}
}
if(!matched) // this is in case you don't have a match
colB.push([""]); // incase we don't have a matching part
} else {
colB.push([rangeTestUsage[i][0]]); // we already have a value we want so just add that to colB array
}
}
sTestUsage.getRange(2,2,lastRowTestUsage).setValues(colB); // update entire column b with values in colB array
}