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.
Related
I have a column "Net Sales" and I am trying to sum all the values in that column in google sheets but using google app scripts. The data for Net Sales will change so I am trying to be as abstract as possible. Here is the function I have so far, the output of the total sum is shown in a separate spreadsheet. Instead of actually adding up all of the sales, this function just puts all the numbers together. For example, if the rows in column Net Sales are 100, 200, and 50, the output would be 10020050 instead of 350. How do I get a function to actually add the numbers together?
//sum of all net sales (not working)
var netSquare = sheet_origin2.getRange(2, 12, sheet_origin2.getLastRow(), 1).getValues();
var sum = 0;
for (var i=0; i<=sheet_origin2.getLastRow(); i++) {
sum += netSquare[i];
}
sheet_destination.getRange(sheet_destination.getLastRow(), 2, 1, 1).setValue(sum);
The last row ≠ the number of rows, especially since you're skipping the first row.
.getValues() returns a 2-d array, so you need to use netSquare[i][0]
You should use the length of the array you're iterating over in your for-loop and also be sure that your index doesn't go out-of-bounds.
function sum() {
// ... define the sheets ...
var lastRow = sheet_origin2.getLastRow();
var numRows = lastRow - 1; // Subtract one since you're skipping the first row
var netSquare = sheet_origin2.getRange(2, 12, numRows, 1).getValues();
var sum = 0;
for (var i=0; i<netSquare.length; i++) {
sum += netSquare[i][0];
}
sheet_destination.getRange(sheet_destination.getLastRow(), 2, 1, 1).setValue(sum);
}
A way more efficient way to calculate the sum is to use reduce and in this way you get rid of for loops.
The sum can be calculated with just the reduce function. All the other functions: flat, map, filter are used to make sure the data is correct since we don't know how your spreadsheet file is constructed and what are the values you are using. See the code comments for detailed explanation of every step.
Solution:
const netSquare = sheet_origin2.getRange('L2:L').getValues(). // get column L (12th column)
flat(). // convert the 2D array to 1D array
filter(v=>v!=''). // filter out empty cells
map(v=>parseInt(v)); // convert string numbers to integers
const sum = netSquare.reduce((a,b)=>a+b); // add all numbers together
sheet_destination.getRange(sheet_destination.getLastRow(), 2, 1, 1).setValue(sum);
The shortest fix for this one would be to type cast since they became string when you fetched the data.
change your:
sum += netSquare[i];
to:
sum += parseInt(netSquare[i]); // if whole number
sum += parseFloat(netSquare[i]); // if number has decimal
This forces the netSquare[i] value to be in type integer/float which can be added as numbers.
There will no be issues when we are sure that netSquare[i] values are all numbers.
For the possible issues, you can check possible outcomes when type casting a non-number data.
I've been trying to write some code that looks down one column with strings based on some simple formulas. I can't seem to get it to recognize the different sets of data and paste them where I want them.
I have tried re writing the code a few different ways in which is looks at all the data and just offsets the destination row by 1. But it does not recognize that it is pull different data.
Below is the code that works. What it does is starts from the 1st column 2nd row (where my data starts). The data is a list like;
A
1 Customer1
2 item1
3 item2
4 Item3
5
6 Customer2
7 Item1
The formulas that I have in those cells just concatenates some other cells.
Using a loop it looks through column A and find the blank space. It then "breaks" whatever number it stops on, the numerical A1 notation of the cell, it then finds the values for those cells and transposes them In another sheet in the correct row.
The issue I am having with the code this code that has worked the best is it doesn't read any of the cells as blank
(because of the formulas?) and it transposes all to the same row.
function transpose(){
var data = SpreadsheetApp.getActiveSpreadsheet();
var input =data.getSheetByName("EMAIL INPUT");
var output = data.getSheetByName("EMAIL OUTPUT");
var lr =input.getLastRow();
for (var i=2;i<20;i++){
var cell = input.getRange(i, 1).getValue();
if (cell == ""){
break
}
}
var set = input.getRange(2, 1, i-1).getValues();
output.getRange(2,1,set[0].length,set.length) .
.setValues(Object.keys(set[0]).map ( function (columnNumber) {
return set.map( function (row) {
return row[columnNumber];
});
}));
Logger.log(i);
Logger.log(set);
}
What I need the code to do is look through all the data and separate the sets of data by a condition.
Then Transpose that information on another sheet. Each set (or array) of data will go into a different row. With each component filling across the column (["customer1", "Item1","Item2"].
EDIT:
Is it Possible to pull different data sets from a single column and turn them into arrays? I believe being able to do that will work if I use "appendrow" to tranpose my different arrays to where I need them.
Test for the length of cell. Even if it is a formula, it will evaluate the result based on the value.
if (cell.length !=0){
// the cell is NOT empty, so do this
}
else
{
// the cell IS empty, so do this instead
}
EXTRA
This code takes your objective and completes the transposition of data.
The code is not as efficient as it might/should because it includes getRange and setValues inside the loop.
Ideally the entire Output Range could/should be set in one command, but the (unanswered) challenge to this is knowing in advance the maximum number rows per contiguous range so that blank values can be set for rows that have less than the maximum number of rows.
This would be a worthwhile change to make.
function so5671809203() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var inputsheetname = "EMAIL_INPUT";
var inputsheet = ss.getSheetByName(inputsheetname);
var outputsheetname = "EMAIL_OUTPUT";
var outputsheet = ss.getSheetByName(outputsheetname);
var inputLR =inputsheet.getLastRow();
Logger.log("DEBUG: the last row = "+inputLR);
var inputrange = inputsheet.getRange(1, 1,inputLR+1);
Logger.log("the input range = "+inputrange.getA1Notation());
var values = inputrange.getValues();
var outputval=[];
var outputrow=[];
var counter = 0; // to count number of columns in array
for (i=0;i<inputLR+1;i++){
Logger.log("DEBUG: Row:"+i+", Value = "+values [i][0]+", Length = "+values [i][0].length);
if (values [i][0].length !=0){
// add this to the output sheet
outputrow.push(values [i][0]);
counter = counter+1;
Logger.log("DEBUG: value = "+values [i][0]+" to be added to array. New Array Value = "+outputrow+", counter = "+counter);
}
else
{
// do nothing with the cell, but add the existing values to the output sheet
Logger.log("DEBUG: Found a space - time to update output");
// push the values onto an clean array
outputval.push(outputrow);
// reset the row array
outputrow = [];
// get the last row of the output sheet
var outputLR =outputsheet.getLastRow();
Logger.log("DEBUG: output last row = "+outputLR);
// defie the output range
var outputrange = outputsheet.getRange((+outputLR+1),1,1,counter);
Logger.log("DEBUG: the output range = "+outputrange.getA1Notation());
// update the values with array
outputrange.setValues(outputval);
// reset the row counter
counter = 0;
//reset the output value array
outputval=[];
}
}
}
Email Input and Output Sheets
What should I do to change the code below (at the very bottom) so that it works for multiple column pairs, not just one column pair?
I am tracking inventory in a sheet, something like this:
ColA ColB ColC ColD ColE ColF ColG ColH
Category Item R+/- G+/- B+/- Red Green Blue
AAA A 1 0 0
AAA B 2 1 0
I want to be able to type numbers into ColC, ColD and ColE and then click a button to subtract those numbers from the totals in ColF, G and H, respectively.
I found a similar question with a great answer here, for ONE column pair:
Google Sheets - How to create add and subtract buttons to track inventory. The code got me started. I'm pretty sure I needed to update the getRange stuff from what it was (shown immediately below) to what is now listed in the whole function code further down. (I also changed some names/variables to better match my inventory needs.)
var updateRange = sheet.getRange(2, 3, maxRows); // row, column, number of rows
var totalRange = sheet.getRange(2, 4, maxRows);
But what do I do with the for section so that it works for all three column pairs, not just for ColC & ColF? I tried adding a "var col in updateValues" but it didn't like col (or column); besides i wasn't sure how to nest it with the var row that's already there. (I did notice that if I changed the 0 after each [row] to 1, it would do the 2nd columns. But it didn't like it when I did "var col in updateValues" and then "updateValue[0][col]".)
function subtractUpdateBulk() {
var sheet = SpreadsheetApp.getActiveSheet();
var maxRows = sheet.getMaxRows();
var updateRange = sheet.getRange(2, 3, maxRows, 3); // row, column, # of rows, # of cols
var totalRange = sheet.getRange(2, 6, maxRows, 3);
var updateValues = updateRange.getValues();
var totalValues = totalRange.getValues();
for (var row in updateValues) {
var updateCellData = updateValues[row][0];
var totalCellData = totalValues[row][0];
if (updateCellData != "" && totalCellData != "") {
totalValues[row][0] = totalCellData - updateCellData;
updateValues[row][0] = "";
}
}
updateRange.setValues(soldValues);
totalRange.setValues(totalValues);
}
If you offer code alone is great. A bit of explanation to go with it would be even better, so I understand the WHY and can hopefully apply it elsewhere.
Your code was pretty close.
This is the substitute code to manage three columns of data movements and three columns of totals. Most of the code is self-explanatory but I'll focus on a couple of points. Its good that you're interested in the why as well as the how, and I've left some DEBUG lines that hopefully with assist.
1) I setup the spreadsheet and sheet using "standard" commands. In this case, I used getSheetByName to ensure that the code always would execute on the desired sheet.
2) I didn't use getMaxRows because this returns "the current number of rows in the sheet, regardless of content.". So if your spreadsheet has 1,000 rows but you've only got, say, 20 rows of data, getmaxRows will return a value of 1,000 and force you to evaluate more rows than are populated with data. Instead I used the code on lines 30 and 30 var Avals and var Alast which use a javascript command to quickly return the number of rows that have data. I chose Column A to use for this, but you could change this to some other column.
3) Rather than declare and get values for two ranges (updateRange and totalRange), I declared only one data range totalRange and got the values for all 6 columns. getValues is a fairly time costly process; by getting values for all rows and all columns, you can then pick and choose which columns you want to add together.
The command is:
var totalRange = sheet.getRange(NumHeaderRows + 1, 3, Alast - NumHeaderRows, 6);
The syntax (as you noted) is row, column, # of rows, # of cols.
The start row is the row following the header => (NumHeaderRows + 1).
The start column is Column C => 3.
The number of rows is the data rows less the header rows => (Alast - NumHeaderRows)
The number of columns is ColC, ColD, ColE, ColF, ColG => 6
4) for (var row in totalValues) {
This was a new one for me, and its so simple, so I keep it.
5) I used two arrays, just as you did. I used one array (RowArray) to build the values for each row, and the second array (MasterArray)is cumulative.
BTW, in your code soldValues isn't ever declared and no values are ever assigned to it.
6) The most important thing is the calculation of the adjustments on each line:
For the sake of clarity, I declared three variables totalRed, totalGreen and totalBlue, and showed how the totals were calculated for each value. This wasn't strictly necessary (I could have just pushed the formulas for each new totals), but they enable you to how each movement is calculated, and the column numbers used in each case.
function so_53505294() {
// set up spreadsheet
// include getsheetbyname to ensure calculations happen on the correct sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
//set some variables
var NumHeaderRows = 1; // this is the number of header rows - user changeable
var totalRed = 0;
var totalGreen = 0;
var totalBlue = 0;
// arrays used later in loop
var RowArray = []; // row by row values
var MasterArray = []; // row by row, cumulative values
// get number of rows of data
var Avals = ss.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
// Logger.log("the last row = "+Alast);// DEBUG
// define the entire data range
var totalRange = sheet.getRange(NumHeaderRows + 1, 3, Alast - NumHeaderRows, 6); // row, column, # of rows, # of cols
//Logger.log("the TotalRange = "+totalRange.getA1Notation());//DEBUG
// get the data fior the entire range
var totalValues = totalRange.getValues();
// loop through thr rows
for (var row in totalValues) {
// clear RowArray at the start of each new row
RowArray = [];
// calculate the new totals
totalRed = totalValues[row][0] + totalValues[row][3];
totalGreen = totalValues[row][1] + totalValues[row][4];
totalBlue = totalValues[row][2] + totalValues[row][5];
//Logger.log("row = "+row+", Movement RED = "+totalValues[row][0]+", Old Stock RED = "+totalValues[row][3]+", New RED = "+totalRed); //DEBUG
//Logger.log("row = "+row+", Movement GREEN = "+totalValues[row][1]+", Old Stock GREEN = "+totalValues[row][4]+", New GREEN = "+totalGreen); //DEBUG
//Logger.log("row = "+row+", Movement BLUE = "+totalValues[row][2]+", Old Stock BLUE = "+totalValues[row][5]+", New BLUE = "+totalBlue); //DEBUG
// update the RowArray for this row's values
RowArray.push(0, 0, 0, totalRed, totalGreen, totalBlue);
// update the MasterArray for this row's values
MasterArray.push(RowArray);
}
// Update the data range with the new Master values.
totalRange.setValues(MasterArray);
}
I'm very new to script writing in general, especially in GAS, and want to make sure I learn good habits. I wrote a for loop script that, in essence, does the following:
Read through column A starting at row 2
If cell above current cell in for loop is the same value, then clear the contents of the adjacent cell to the right of the current cell.
For example
Cell A1 contains "Something". Cell B1 contains "Exciting"
Cell A2 contains "New". Cell B2 contains "X"
Cell A3 contains "New". Cell B3 contains "Y"
Since A3 has the same value as cell A2 (that value being "New"), cell B3 (value currently "Y") is cleared so that there is no value in cell B3.
It seems to take a very long time to run, which I am sure is due to my novice writing, and I want to make this script as efficient as possible.
Do any of you scripting gods have any suggestions to make this script more efficient?
I would also appreciate any explanation as to why any suggestion would be more efficient for my own understanding, and so that anyone that happens to find this posting, later on, can understand it as well.
Here is the script:
function testCode() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Test 1");
var source = sheet.getRange("A2:A");
for (i = 2; i < source.getLastRow(); i++) {
var currentCell = sheet.getRange(i,1).getValue();
var cellAbove = sheet.getRange(i-1,1).getValue();
var cellRight = sheet.getRange(i,2);
if (currentCell == cellAbove){
cellRight.clearContent(),({contentsOnly:true});
}
}
}
THANK YOU
The biggest issue is that you are getting three new ranges in every loop. There is a way to do this without getting new ranges in the loop. You'll need to get the data in both columns A and B.
function testCode() {
var cellAbove,currentCell,cellRight,data,lastRow,L,sheet,source,ss;
ss = SpreadsheetApp.getActiveSpreadsheet();
sheet = ss.getSheetByName("Test 1");
lastRow = sheet.getLastRow();
source = sheet.getRange(2,1,lastRow-1,2);//Get data starting in row 2 and column 1
//Get the number of rows that are the lastRow minus the number of rows not included
//at the top - get 2 columns
data = source.getValues();//Get a 2D array of values
L = data.length;//Get the number of elements in the outer array- which is the number of
//rows in the array
for (var i = 0; i < L; i++) {
if (i == 0) {continue;} //If this is the first loop there is no value above it to check
currentCell = data[i][0];
cellAbove = data[i-1][0];
if (currentCell == cellAbove){
cellRight = sheet.getRange(i+1,2);
cellRight.clearContent(),({contentsOnly:true});
}
}
}
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.