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);
Related
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);
}
```
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))
Basically I have a script that is in 4 blocks:
1. Copies within a range each row provided it meets a criteria
2. Removes all empty rows
3. Sets all numbers as percentage
4. Applies conditional cell formatting to one of the columns
The 4th part is the one that is causing me issues. The script runs without any error message AND block 4 works perfectly fine if it's in another script alone with the same variables defined but as soon as it is inside the same function as the others it simply doesn't run without any error message of any kind.
Tried changing the name of the variables to single use ones to ensure it wasn't because one of the "var" was modified above it, removing the "else if" to keep only an "if" in the loop, moving it around to other parts of the script but if the block 1 is in the script then block 4 won't apply (will apply if it is only with 2 & 3.
2 & 3 which follow the same structure work well with 1.
Does any one have any clue what's wrong with my script ? :)
Each block is commented with what it does
function copy() {
//Set variables & criterion to choose which rows to copy
var s = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/1bEiLWsbFszcsz0tlQudMBgTk5uviyv_wDx7fFa8txFM/edit');
var ssSource = s.getSheetByName('Variations');
var ssDest = s.getSheetByName('Email');
var lastRowSource = ssSource.getLastRow();
var lastRowDest = ssDest.getLastRow();
var lastColSource = ssSource.getLastColumn()
var criteria = 0;
var titles = ssSource.getRange(1,1,1, lastColSource).getValues()
//Copies the range
ssDest.getRange(1,1,1, lastColSource).setValues(titles)
for (var i = 2; i < lastRowSource; i++ ) {
var test = ssSource.getRange(i ,1);
Logger.log(test.getValue()+ ' <? ' + criteria);
if (ssSource.getRange(i ,6).getValue() > criteria) {
ssSource.getRange(i ,1,1,ssSource.getLastColumn()).copyTo(ssDest.getRange(i ,1,1,ssSource.getLastColumn()), {contentsOnly:true}); // copy/paste content only
}
}
//Removes empty rows
var data = ssDest.getDataRange().getValues();
var targetData = new Array();
for(n=0;n<data.length;++n){
if(data[n].join().replace(/,/g,'')!=''){ targetData.push(data[n])};
Logger.log(data[n].join().replace(/,/g,''))
}
ssDest.getDataRange().clear();
ssDest.getRange(1,1,targetData.length,targetData[0].length).setValues(targetData);
//Formats numbers as percentages
var rangePercent = ssDest.getRange(1,1,ssDest.getLastRow(),ssDest.getLastColumn());
var rowsPercent = rangePercent.getNumRows();
var colsPercent = rangePercent.getNumColumns();
for(var rowPercent = 1; rowPercent <= rowsPercent; rowPercent++) {
for(var colPercent = 1; colPercent <= colsPercent; colPercent++) {
var cellPercent = rangePercent.getCell(rowPercent, colPercent);
var valuePercent = cellPercent.getValue();
if(typeof(valuePercent) == 'number') {
cellPercent.setNumberFormat("##.#%");
}
}
}
//Adds conditional background colours
for (var z = 2; z < lastRowDest+1;z++) {
var avgCpc = 4;
var rangeColour = ssDest.getRange(z,avgCpc);
var dataColour = rangeColour.getValue()
if (dataColour < 0) {
ssDest.getRange(z,avgCpc).setBackground('#d9ead3')
}
else if (dataColour > 0) {
ssDest.getRange(z,avgCpc).setBackground('#f4cccc')
}
}
//Centers Values
}
The problem you're having is your code has performance issues because you're calling too many times methods such as getRange() and getValue() inside various loops, therefore Apps Script can't keep up with all those calls. Please check Best Practices.
Having said that, I modified your code in order to make it more efficient. Besides your copy function, I added two more functions to make the code more readable.
function copy
As before this function sets the variables, but now it calls two other functions, which are setPositiveCostValues and formatCells
function copy() {
//Set variables & criterion to choose which rows to copy
var ss = SpreadsheetApp.openByUrl('your-url');
var ssSource = ss.getSheetByName('Variations');
var ssDest = ss.getSheetByName('Email');
// set the title
var titles = ssSource.getRange(1,1,1, ssSource.getLastColumn()).getValues();
ssDest.getRange(1,1,1, ssSource.getLastColumn()).setValues(titles);
// get the positive values you want from the cost col
var positiveValues = setPositiveCostValues(ssSource, ssDest, ssSource.getLastRow());
// fomrat the cells you want as percentage and set the color
formatCells(ssDest, positiveValues);
}
function setPositiveCostValues
This will take the values where the cost is positive and it will get rip off of the cells with empty values and "n/a" values.
function setPositiveCostValues(ssSource,ssDest, lastRowSource){
var postiveCost = ssSource.getRange(2, 1, lastRowSource, 6).getValues();
// this loop will clean the empty elements and the ones that only have n/a
for (var i = postiveCost.length - 1; i >= 0; i--) {
if (postiveCost[i][0]) {
postiveCost.splice(i + 1, postiveCost.length - (i + 1));
postiveCost = postiveCost.filter(function(el){ return el != 'n/a'})
break;
}
}
return postiveCost;
}
function formatCells
This will format the cells in the cost col as a percentage and will set the right color in your avgCpc col.
function formatCells(ssDest, postiveCost){
var avgCpc = 4, cost = 6, row = 2, criteria = 0;
// iterate over the array and depending on the criteria format the cells
postiveCost.forEach(function(el){
if(el[cost - 1] > criteria){
var ssDestRange = ssDest.getRange(row, 1, 1, cost);
ssDestRange.setValues([el]);
ssDestRange.getCell(1, cost).setNumberFormat("##.#%");
// set the color depending on the avgCpc value condition
if(el[avgCpc - 1] < criteria) ssDest.getRange(row, avgCpc).setBackground('#d9ead3');
else ssDest.getRange(row, avgCpc).setBackground('#f4cccc');
row++;
}
});
}
Code
Your whole code now it will look like this:
function copy() {
//Set variables & criterion to choose which rows to copy
var ss = SpreadsheetApp.openByUrl('your-url');
var ssSource = ss.getSheetByName('Variations');
var ssDest = ss.getSheetByName('Email');
// set the title
var titles = ssSource.getRange(1,1,1, ssSource.getLastColumn()).getValues();
ssDest.getRange(1,1,1, ssSource.getLastColumn()).setValues(titles);
// get the positive values you want from the cost col
var positiveValues = setPositiveCostValues(ssSource, ssDest, ssSource.getLastRow());
// fomrat the cells you want as percentage and set the color
formatCells(ssDest, positiveValues);
}
function setPositiveCostValues(ssSource,ssDest, lastRowSource){
var postiveCost = ssSource.getRange(2, 1, lastRowSource, 6).getValues();
// this loop will clean the empty elements and the ones that only have n/a
for (var i = postiveCost.length - 1; i >= 0; i--) {
if (postiveCost[i][0]) {
postiveCost.splice(i + 1, postiveCost.length - (i + 1));
postiveCost = postiveCost.filter(function(el){ return el != 'n/a'})
break;
}
}
return postiveCost;
}
function formatCells(ssDest, postiveCost){
var avgCpc = 4, cost = 6, row = 2, criteria = 0;
// iterate over the array and depending on the criteria format the cells
postiveCost.forEach(function(el){
if(el[cost - 1] > criteria){
var ssDestRange = ssDest.getRange(row, 1, 1, cost);
ssDestRange.setValues([el]);
ssDestRange.getCell(1, cost).setNumberFormat("##.#%");
// set the color depending on the avgCpc value condition
if(el[avgCpc - 1] < criteria) ssDest.getRange(row, avgCpc).setBackground('#d9ead3');
else ssDest.getRange(row, avgCpc).setBackground('#f4cccc');
row++;
}
});
}
I have written that has a for loop that will run additional code if (sheetData[i] !== "").
Ideally, I would like to run the following code after the condition, but then loop back around and run it again for the next item that matches the condition. Any ideas on how I can achieve this?
function getSheetSectionDataTest(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Params"); // get sheet
var sheetData = sheet.getDataRange().getValues(); //get all sheet data
var sectionNames = normalizeHeaders(normalizeHeaders(sheet.getRange('A1:A').getValues()));
var sectionData = []; // main array to contain all section data
// create an array for each section
for(h = 0; h < sectionNames.length; h++) {
sectionData[sectionNames[h]] /*property name or key of choice*/
= [];
}
for (var i = 0; i < sheetData.length; i++){ //loop through each row in the spreadsheet
var sectionName = normalizeHeaders(sheetData[i]); //return normalized camelCase section name found in column A.
if (sheetData[i] !== ""){ // Test - stop at a cell that matches the criteria and return the data table.
var headerRow = normalizeHeaders(sheetData[i+1]); //define and normalize the table headers
for (var j = i+2; j < sheetData.length; j++) { //loop through each row in the data table.
if (sheetData[j][1] !== ""){ //if there are contents in the table keep looping.
var obj = {};
sectionData[sectionName[0]].push(obj); // Need to replace ranges with a dynamic variable that gives us the current sectionName value as an object?
for (var rowColumn = 0; rowColumn < headerRow.length; rowColumn++) { //loop through each column in the current row of the table.
obj[headerRow[rowColumn]]=sheetData[j][rowColumn+1];
}
}
else { //stop when an empty cell is reached
return sectionData; //when the data table loop runs into an empty cell stop loop and return the data
}
}
}
}
};
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
}