So Im have 2 columns in google spreadsheet, A and B, and I want to compare them row by row (A1 to B1, A2 to B2, etc), and finally count the number of cells that has the exact same value (can be a string or integer, but have to be identical) and put it in another cell, D1 for example. This is what I got so far, but it doesnt seem to do anything, and doesnt return any error either.
function compare() {
var sss = SpreadsheetApp.getActiveSpreadsheet();
var ss = sss.getSheetByName('theSheetINeed');
var range1 = ss.getRange('A1:A'); //the first column
var data1 = range1.getValues();
var range2 = ss.getRange('B2:B'); //the second column
var data2 = range2.getValues();
var count = []; //to count the number of match
for(var i =0;i 'smaller than' data1.length; i++){ //somehow i cant use '<'
var abc = data1[i];
var def = data2[i];
if(abc == def){
count += count;
};
};
ss.getRange('D1').setValue(count.length);
}
Edit: so my code actually does something, it returns 0 everytime...
Modification points :
Values retrieved by getValues() are 2 dimensional array.
count is defined as an array. But it is not used as array.
i 'smaller than' data1.length is i<data1.length.
Starting row for column A and B are 1, 2, respectively.
Cells without values are included. So when such cells each other are compared, the values become the same. (If you want to compare such cells, please remove && abc && def from following script.)
Modified script :
Your script can be written by modifying above points as follows.
function compare() {
var sss = SpreadsheetApp.getActiveSpreadsheet();
var ss = sss.getSheetByName('theSheetINeed');
var range1 = ss.getRange('A1:A'); //the first column
var data1 = range1.getValues();
var range2 = ss.getRange('B2:B'); //the second column
var data2 = range2.getValues();
var count = []; //to count the number of match
for(var i=0; i<data1.length-1; i++){ //somehow i cant use '<'
var abc = data1[i][0];
var def = data2[i][0];
if(abc == def && abc && def){
count.push(abc);
};
};
ss.getRange('D1').setValue(count.length);
}
If I misunderstand your question, I'm sorry.
An alternative to an Apps Script needing to run in the background you can do this with out-of-the-box formulas, and therefore the result is live
=SUM(
QUERY(
FILTER(A1:B,A1:A<>"",A1:A<>0),
"Select count(Col1)
Where Col1=Col2
Group By Col1
Label count(Col1) ''"
,0)
)
The advantage of formula based solutions is that they are more visible, and anyone following you can be sure the answer is correct without knowing they have to run a script to achieve this.
Breaking the formula down and starting in the middle:
FILTER(A1:B, A1:A<>"", A1:A<>0)
this returns all the rows where there is a non-empty cell. I do this because QUERY can be misleading with finding blank cells
"Select count(Col1)
Where Col1=Col2
Group By Col1
Label count(Col1) ''"
This does the comparisons you asked for and returns a count for each of the values in A that have a match in B. The Select here uses Col1 instead of A because the FILTER returns an Array not a
Range.
From then the SUM adds up each of those totals.
Related
Good Day! I would like the Purchase Order ID repeated upon submission of a form in an earlier sheet. My current code however repeats this throughout the length of data range. Can I get help in modifying the code or loop/if statement below? Thank you in advance.
function exportPO() {
var myGooglSheet = SpreadsheetApp.getActive();
var shSource = myGooglSheet.getSheetByName("Source");
var shDatabase = myGooglSheet.getSheetByName("Database");
var lastRow = shDatabase.getLastRow()
var ProductValues = shSource.getRange("B5:C14").getValues();
shDatabase.getRange(lastRow+1,2,ProductValues.length,2).setValues(ProductValues);
var PO = shSource.getRange("C1").getValues();
var lines = 0;
var ProductRow = ProductValues.length;
for(var i=0; i<ProductRow; i++)
if (ProductValues!=''){
lines= lines+1;
shDatabase.getRange(lastRow+lines,1).setValues(PO);
shDatabase.getRange(lastRow+lines,4).setValue(new Date()).setNumberFormat('yyyy-mm-
dd h:mm'); //Submitted On
In relation to
var ProductValues = shSource.getRange("B5:C14").getValues();
The above line assigns a Array of Arrays to ProductValues
In relation to
ProductValues!=''
The above expression always will return false. You might change this to ProductValues[i].every(value => value != '') to compare all the values in a row to an empty string, if all are not a empty string, this will return true, otherwise false, or change the original expression to something like ProductValues[i][0]!='' to compare on value of each row to an empty string, the first index corresponds to the row, the second to the column.
I am trying to compare the data from 2 google sheets. Each sheet has a column that is the identifier (sheet1:H and sheet2:C), if these match then I want to change sheet1:I to the value in sheet2:E. I'm running this code, but get no errors. It's not working though.
I tried to see similar posts this issue but they all seem to be lacking the compare a different column method I am using.
function changestatus() {
// gets spreadsheet A and the range of data
ssA = SpreadsheetApp.openById('IDHERE');
sheetA = ssA.getSheetByName('Sheet1');
dataA = sheetA.getRange('H2:H').getValues();
dataD = sheetA.getRange('I2:I').getValues();
// gets spreadsheet B and the range of data
ssB = SpreadsheetApp.openById('IDHERE');
sheetB = ssB.getSheetByName('responses');
dataB = sheetB.getRange('C2:C').getValues();
dataC = sheetB.getRange('E2:E').getValues();
for (var i = 0; i > sheetA.getLastRow(); i++) {
if (dataA[1][i] == dataB[1][i] && dataC[1][i] != dataD[1][i]){
var value = sheetA.getRange(i+1, 2).getValue(dataD);
sheetB.getRange(i+1, 2).setValue(value);
} // end if
} // end i
Starting results of sheets files would be something like:
Sheet 1
H:(ID) 1 I:(grade) pass
Sheet 2
C:(ID) 1 E:(grade) fail
After Function:
Sheet 1
H:(ID) 1 I:(grade) fail
#tehhowch is quite right; you need to review JavaScript comparison operators, for loop syntax, the format of object returned by Range#getValues, and how to access JavaScript array indices. Each of these contributes to your code problems, but it's reasonable that that we help you along the road a little more.
Loop syntax
This is an easy one. Instead of "i > sheetA.getLastRow()", it should read i < sheetA.getLastRow(). i starts with a value of zero, and its value increases by one at the end of each loop; so you want the loop to process all the values of i that are less than the value of the last row.
Array values
getValues returns a two-dimensional array but the IF statement fails because the array values are back to front.
For example, instead of "dataA[1][i]", it should be dataA[i][0]. There are two changes here:
1 - "i" moves to the first half of the array value (the 'row' value); and
2 - the second half of the array value is [0] (not "[1]"). This is because each variable is only one column wide. For example, dataA only returns the value of column H; same is true for dataB, dataC and dataD - they all return the value of just one column.
Troubleshooting
How could you tell whether the IF statement was a problem? It "looks" OK. One way is to display (or log) the values being returned.
I use Logger.log() (there are other options) to display information in the script editor under "View, Logs". Each time the script is run, the "Logger" statements are updated and you can check their value.
For example, you could insert this code at line 13 (before the loop) to check some values of the data variables.
Logger.log("dataA[1][0] = "+dataA[1][0]);
That line will show: "dataA[1][0] = 2". That's a valid result but you might notice that it is reporting ID=2 but, say, you were expecting a result of ID=1.
So change the line to:
Logger.log("dataA[1][1] = "+dataA[1][1]);
This line shows "dataA[1][1] = undefined". OK, something definitely wrong.
So, let's try:
Logger.log("dataA[0][0] = "+dataA[0][0]);
This line shows "dataA[0][0] = 1". Now that's more like it.
You can make Logger long or short; for example, you might want to evaluate the results of of the variables in one line. So the Logger might look like this:
Logger.log("dataA[0][0] = "+dataA[0][0]+", dataB[0][0] = "+dataB[0][0]+", dataC[0][0] = "+dataC[0][0]+", dataD[0][0] = "+dataD[0][0]);
And it would return:
"dataA[0][0] = 1, dataB[0][0] = 1, dataC[0][0] = Fail, dataD[0][0] = Pass".
This might confirm that you are on the right track, or that you need to debug further
The Failing IF statement
Original line = "(dataA[1][i] == dataB[1][i] && dataC[1][i] != dataD[1][i])"
Corrected line = (dataA[i][0] == dataB[i][0] && dataC[i][0] != dataD[i][0])
Updating the results on Sheet 1
The code here is:
var value = sheetA.getRange(i+1, 2).getValue(dataD);
sheetB.getRange(i+1, 2).setValue(value);
This is confusing and complicates a couple of things.
1 - the value just needs to be "the value in sheet2:E - this was in the IF statement: dataC[i][0]. So value = dataC[i][0]
2 - The goal is "change sheet1:I to the value in sheet2:E". You've already got the value, so focus now on sheet1:I.
Some times it is more simple to define the range and then, on a second line, update the value for that range.
the target sheet is sheetA;
the target row is: i+1 (that was correct);
the target column is: I (or column 9).
So, var range = sheetA.getRange(i+2, 9);
You could check this with "Logger":
Logger.log("range = "+range.getA1Notation()); might return "range = I2".
Then update the value:
range.setValue(value);
Meaningful variable names
It helps (a LOT) to use meaningful variable names. For example, the original code uses:
"dataA" = Sheet1, Column H (contains ID); so maybe this could be "data1_H" or even "targetID.
"dataD" = Sheet1, Column I (contains grade); so maybe this could be "data1_I" or targetGrade.
"dataB" = Sheet2, Column C (contains ID), so maybe this could be "data2_C" or sourceID.
"dataC" = Sheet2, Column E (contains grade); so maybe this could be "data2_E" or sourceGrade.
Summary of changes
function so_changestatus() {
// gets spreadsheet A and the range of data
ssA = SpreadsheetApp.openById('IDHERE');
sheetA = ssA.getSheetByName('Sheet1');
dataA = sheetA.getRange('H2:H').getValues();
dataD = sheetA.getRange('I2:I').getValues();
// gets spreadsheet B and the range of data
ssB = SpreadsheetApp.openById('IDHERE');
sheetB = ssB.getSheetByName('responses');
dataB = sheetB.getRange('C2:C').getValues();
dataC = sheetB.getRange('E2:E').getValues();
for (var i = 0; i < sheetA.getLastRow(); i++) {
if (dataA[i][0] == dataB[i][0] && dataC[i][0] != dataD[i][0]){
var value = dataC[i][0];
var range = sheetA.getRange(i+2, 9);
range.setValue(value);
} // end if
}
}
UPDATE - 1 April 2019
ID on SheetA vs SheetB does NOT match row-by-row
The original code was written on the basis that the ID matched on a row-by-row basis. This is not the case. So a variation in the code is needed to test whether the ID on SheetA exists on SheetB, and then test the respective status.
The evaluation of the sheetA ID on sheetB is done with [indexof] Docs reference.
In this code, I also took the opportunity to make the variable names of the data ranges more meaningful.
Note also: the loop continues while i is less than the lastrow minus one "i < (lastrow-1);". This is necessary because the first row are headers and the data range starts on row 2, so the number of data rows will be the "lastrow minus one" (to a allow for the header row).
function ejb2so_changestatus() {
// gets spreadsheet A and the range of data
// ssA = SpreadsheetApp.openById('IDHERE');
ssA = SpreadsheetApp.getActive();
sheetA = ssA.getSheetByName('Sheet1');
dataA_ID = sheetA.getRange('H2:H').getValues();
data_Status = sheetA.getRange('I2:I').getValues();
//Logger.log("DEBUG: H3 = "+dataA_ID[4][0]+", I3 = "+data_Status[4][0]);//DEBUG
// gets spreadsheet B and the range of data
//ssB = SpreadsheetApp.openById('IDHERE');
ssB = SpreadsheetApp.getActive();
sheetB = ssB.getSheetByName('Responses');
dataB_ID = sheetB.getRange('C2:C').getValues();
dataB_Status = sheetB.getRange('E2:E').getValues();
// Logger.log("DEBUG: C3 = "+dataB_ID[0][0]+", E3 = "+dataB_Status[0][0]);//DEBUG
var lastrow = sheetA.getLastRow()
// Logger.log("DEBUG: sheetA last row = "+lastrow);//DEBUG
// Flatten the array
var dataB_IDFlat = dataB_ID.map(function(row) {
return row[0];
});
//Loop through values on sheetA; check if they exist on sheetB
for (var i = 0; i < (lastrow - 1); i++) {
var A_ID = dataA_ID[i][0];
// Logger.log("DEBUG: id = "+A_ID);//DEBUG
// assign variable to return value index
var result = dataB_IDFlat.indexOf(A_ID);
if (result != -1) {
// it's there
// Logger.log("DEBUG: i: "+i+", ID: "+A_ID+", it's there"+", result#: "+result);//DEBUG
// Logger.log("DEBUG: Sheet1 status: "+data_Status[i][0]+" Vs Sheet2 status = "+dataB_Status[result][0]);//DEBUG
// compare status from sheetsA to sheetB
if (data_Status[i][0] != dataB_Status[result][0]) {
// Logger.log("DEBUG: status change to: "+dataB_Status[result][0]);//DEBUG
var range = sheetA.getRange(i + 2, 9);
//Logger.log("DEBUG: value = "+value);//DEBUG
//Logger.log("DEBUG: range = "+range.getA1Notation());//DEBUG
range.setValue(dataB_Status[result][0]);
}
} else {
// it's not there
// Logger.log("DEBUG: i: "+i+", ID: "+A_ID+", it's not there");//DEBUG
}
}
}
// Credit: Flatten array: https://stackoverflow.com/a/49354635/1330560
I have a loop that work to match row value from bottom and it goes like this:-
var lastRow = s3.getLastRow();
var dataRange = s3.getRange(1, 1,lastRow).getValues();
for(var k=0;k<dataRange.length;k++)
{doing something}
However, I am getting no result when I am trying to do the same thing with column match, here is my loop for column match that does not do anything.
var lastColumn = s3.getLastColumn();
var match2 = s3.getRange(1, 1,lastColumn).getValues();
for (var b = 0; b < match2.length; b++)
{if (range[j][0] == match2[0][b])
{ do something }
}
Please suggest what I am missing.
This is taken right out of the documentation:
getRange(row, column, numRows) Range Returns the range with the top left cell at the given coordinates, and with the given number of rows.
match2.length is the number of rows in the array or the range.
This array [[x,x,x],[y,y,y],[z,z,z]...] has three x's in the first row, 3 y's in the second row and so on. So in s3.getRange(1, 1,lastColumn).getValues(); lastColumn is the number of rows in that range. Essentially it's easier to read each row and then get the column one at a time. Or you could transpose your data like a matrix and then read the columns that are now rows.
A loop looking for "Big Macs':
function myFunction()
{
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var rg=ss.getRange("A1:Z1");
var vA=rg.getValues();
for(var i=0;i<vA.length;i++)
{
for(j=0;j<vA[0].length;j++)
{
if(vA[i][j]=="Big Mac")
{
SpreadsheetApp.getUi().alert('Do not eat this burger as it has massive amounts of fat in it.');
break;
}
}
}
}
In these two dimensional arrays obtained by commands such as var data = range.getValues(); data.length = the number of rows and data[0].length = the number of columns. So total number of array elements data.length x data[0].length some of which may be null. Many programmers new to Google Apps Scripting have problems in this area. In fact I had a lot of trouble with it so I ended up doing some extra work to help bolster my understanding and you can read about it here.
These arrays look like the following: [[0,1,2,3,4,5...],[0,1,2,3,4,5...],[0,1,2,3,4,5...]...]. So vA is an array of arrays and so the term vA.length is equal to the number of elements in vA and simply put it's also equal to the number rows.
I am trying to loop through the whole row in my google sheet and copy some of the data from one sheet to another. The list will get longer over time.
More specifically: If input in column B equals "blue", than copy the values from column A and C into another sheet.
Do this for all columns till the end of the column.
Link to my spreadsheet: https://docs.google.com/spreadsheets/d/1xnLygpuJnpDfnF6LdR41gN74gWy8mxhVnQJ7i3hv1NA/edit?usp=sharing
The loop stops when the colour does not equal blue. Why?
As you can see I used a for loop. Is that even the way to go?
Can I do anything about the speed of the code execution?
Any comments, hints or help are highly appreciated.
Regards!
You had the input sheet named "List" and I named the output sheet "Output". And here's the code.
function condCopy()
{
var s = SpreadsheetApp.getActiveSpreadsheet();
var sht = s.getSheetByName('List')
var drng = sht.getDataRange();
var rng = sht.getRange(2,1, drng.getLastRow()-1,drng.getLastColumn());
var rngA = rng.getValues();//Array of input values
var rngB = [];//Array where values that past the condition will go
var b = 0;//Output iterator
for(var i = 0; i < rngA.length; i++)
{
if(rngA[i][1] == 'blue')
{
rngB[b]=[];//Initial new array
rngB[b].push(rngA[i][0],rngA[i][2]);
b++;
}
}
var shtout = s.getSheetByName('Output');
var outrng = shtout.getRange(2,1,rngB.length,2);//Make the output range the same size as the output array
outrng.setValues(rngB);
}
You have 2 options. The first is to use the standard query() function from Google Sheets to get the values. The downside here is that it is only a reference of the values. So you cannot reorder them, etc. To use this, place this in cell A1 and it will pull the Headers and retrieve the values from column A and C:
=QUERY(A:C, "select A, C where B = 'blue'", 1)
For a Google Apps Script answer:
This will loop through your List sheet and for every row where column B is blue it will save the values in column A and C to column A and B of the new sheet:
function doIt(){
var activeSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet4");
var lastRow = activeSheet.getLastRow();
var lastCol = activeSheet.getLastColumn();
var targetValues = [];
var sourceSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("List");
var lastSourceRow = sourceSheet.getLastRow();
var lastSourceCol = sourceSheet.getLastColumn();
var sourceRange = sourceSheet.getRange(1, 1, lastSourceRow, lastSourceCol);
var sourceData = sourceRange.getValues();
var activeRow = 0;
//Loop through every retrieved row from the Source
for (row in sourceData) {
//IF Column B in this row has 'blue', then work on it.
if (sourceData[row][1] === 'blue') {
//Save it ta a temporary variable
var tempvalue = [sourceData[row][0], sourceData[row][2]];
//then push that into the variables which holds all the new values to be returned
targetValues.push(tempvalue);
}
}
//Save the new range to the appropriate sheet starting at the last empty row
activeSheet.getRange(lastRow + 1, 1 , targetValues.length, 2).setValues(targetValues);
}
Of course, you could pass the value to test to the function by replacing 2 lines. The first, defining the function:
function doIt(testingvar){
to pass a variable called testingvar, and the test line to replace the hard coded test with the passed variable:
if (sourceData[row][1] === testingvar) {
I had success filling a single column (A) with the value found in range A1...
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var lastRow = ss.getDataRange().getNumRows();
var rngVal = ss.getRange("A1").getValue()
ss.getRange("A2:A"+lastRow).setValue(rngVal)
So then I thought I was on easy-street, and I tried to modify/apply that to a larger range by filling a multi-column range with the values found in range C1:H1...
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var lastRow = ss.getDataRange().getNumRows();
var rngVal = ss.getRange("C1:H1").getValues()
ss.getRange("C2:H"+lastRow).setValues(rngVal)
Apparently there is a bit more to this than simply slapping an "S" onto the end of the word "Value".
The error reads as follows:
Incorrect range height, was 1 but should be 10
(FYI: var lastRow = 11)
Btw, I get no error if I use Value instead of Values, although I end up with cells full of the value found only in range C1.
So I'm close.... or way off. One of those.
Help???
The error message is quite explicit... the size of the array must fit into the range in both getValues and setValues. Try it like this
:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var lastRow = ss.getLastRow()
var rngVal = ss.getRange("C1:H"+lastRow).getValues();// get an array of 10 "rows" and 6 "columns"
ss.getRange("C2:H"+(lastRow+1)).setValues(rngVal);//write back this array to a range that has the same size. (starting from Row2 it must ends on lastRow+1 to keep the same number of "rows"
}
This function will shift the range C1:H last Row to C2:H lastRow+1, not sure it is very useful but that's not the point here ;-)
EDIT : sorry, I didn't understand exactly your requirement... here is a code that reproduce data from C1:H1 in all rows below
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var lastRow = ss.getLastRow()
var rngVal = ss.getRange("C1:H1").getValues();// get an array of the first row
var rowData = rngVal[0]
var newData = []
for(n=1;n<lastRow;++n){newData.push(rowData)}
ss.getRange("C2:H"+lastRow).setValues(newData);//write back this array to a range that has the same size. (starting from Row2 it must ends on lastRow+1 to keep the same number of "rows"
}
EDIT2 following your comment below :
A word of explanation :
When using range.getValues() we get a 2 dimensions array, meaning an array of arrays that can be represented as follow : [[data1,data2,data3],[data4,data5,data6]] data1, 2 & 3 are the value of the first array (index 0) and data 4,5 & 6 are the values of the second array (index 1). So if you want to get the values in the first array you have to write it like this : value = arrayName[0] and this will return a one dimension array [data1,data2,data3], that's what I used to get rowData.
Now we need to get a 2 dimension array again to be able to write back the new data to a range in the spreadsheet. Therefor we create a new array (var newData=[] or var newData = new Array() does exactly the same), and in the for loop we add the rowData array to this new array... the result will be an array of arrays, that is actually what we were looking for and we can write this directly to the sheet in one single setValues statement.
Ok, this seems to do it...
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var lastRow = ss.getLastRow()
var rngVal = ss.getRange("C1:H1").getValues()
for (var x=1; x<=lastRow; x++) {
ss.getRange("C"+x+":H"+x).setValues(rngVal);