I'm trying to set a formula in Google Sheet from Google App Script.
Here's the illustration of the Spreadsheet:
I also tried to make the code but I don't know how to loop it so for every values added to cell range 'A3:F' will automatically SUM it to the Total column (column G). Could you show me how to loop it? Your response will be appreciated :)
function onEdit(e){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName('Sheet1');
var cell = s.getRange("G3");
cell.setFormula("=SUM((A3/100)*D3)+((B3/100)*E3)+((C3/100)*F3)");
}
EDIT
Here's the updated code that works for me:
function onEdit(e){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName('Sheet1');
const cell = s.getRange(3, 7, s.getLastRow());
cell.setFormula("=SUM((A3/100)*D3)+((B3/100)*E3)+((C3/100)*F3)");
}
Loop is not needed. SetFormula will automatically adjust to a new range, as if autofilled:
const cell = s.getRange(`G3:G${s.getLastRow()}`);
cell.setFormula("=SUM((A3/100)*D3)+((B3/100)*E3)+((C3/100)*F3)");
You can have a "template" for your formula and replace the necessary params with the row number in a loop. Here's an example that sets the formula from row 3 to row 12
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName( 'Sheet1' );
var firstRowNum = 3;
var lastRowNum = 12;
var formulas = [];
var formulaTemplate = '=SUM((A{rowNum}/100)*D{rowNum})+((B{rowNum}/100)*E{rowNum})+((C{rowNum}/100)*F{rowNum})';
for ( var i = firstRowNum; i <= lastRowNum; i++ ) {
formulas.push( [ formulaTemplate.replace( /{rowNum}/g, i ) ] );
}
s.getRange( 'G' + firstRowNum + ':G' + lastRowNum ).setFormulas( formulas );
You can do this with a 'simple' function instead of scripts.
SUMPRODUCT is good but it doesn't loop down. You can use the ARRAYFORMULA, but you'll need to list out each multiplication like A/100*D.
To loop down with ARRAYFORMULA, each column range would be in the format A3:A etc.
Your formula in cell G3 would therefore be:
=ARRAYFORMULA((A3:A/100*D3:D)+(B3:B/100*E3:E)+(C3:C/100*F3:F))
NOTE: row numbers need to be the same as where you've inserted the main ARRAYFORMULA (ie. 3 as per the example). If you get it wrong, your sheet can generate a huge number of rows before you know it!!
Related
My spreadsheet has a column (A) with over 1000 rows of values like 10.99€, 25.99 € and so on. for optimizing purposes, I am looping through this column and removing the "EUR" mark and replacing "." with ",". While the code works, my problem is that it takes super long to execute and for thousands of products it sometimes time outs. I know I am probably not following the best practices, but this was the best solution I could come up with because of my limited JavaScript skills. Any help?
function myFunction() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Table');
var lastRow = sheet.getRange(1,1).getDataRegion(SpreadsheetApp.Dimension.ROWS).getLastRow();
for (var i = 1; i < lastRow +1; i++) {
var price = sheet.getRange(i,1).getValue();
var removeCur = price.toString().replace(" EUR","").replace(".",",");
sheet.getRange(i,1).setValue(removeCur);
}
}
It's a classic question. Classic answer -- you need to replace cell.getValue() with range.getValues(). To get this way 2D-array. Process the array with a loop (or map, etc). And then set all values of the array at once back on sheet with range.setValues()
https://developers.google.com/apps-script/guides/support/best-practices?hl=en
For this case it could be something like this:
function main() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Table');
var range = sheet.getDataRange();
var data = range.getValues(); // get a 2d array
// process the array (make changes in first column)
const changes = x => x.toString().replace(" EUR","").replace(".",",");
data = data.map(x => [changes(x[0])].concat(x.slice(1,)));
range.setValues(data); // set the 2d array back to the sheet
}
Just in case here is the same code with loop for:
function main() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Table');
var range = sheet.getDataRange();
var data = range.getValues();
for (var i=0; i<data.length; i++) {
data[i][0] = data[i][0].toString().replace(" EUR","").replace(".",",")
}
range.setValues(data);
}
Probably the loop for looks cleaner in this case than map.
And if you sure that all changes will be in column A you can make the script even faster if you change third line in the function this way:
var range = sheet.getRange("A1:A" + sheet.getLastRow());
It will narrow the range to one column.
Well, there's something you can do to improve your code, can't guarantee it will help you to make it faster, but we'll see.
Here's the updated version
function myFunction() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Table');
var lastRow = sheet.getRange(1,1).getDataRegion(SpreadsheetApp.Dimension.ROWS).getLastRow() + 1;
var price;
var removeCur;
for (var i = 1; i < lastRow; i++) {
price = sheet.getRange(i,1).getValue();
removeCur = price.toString().replace(" EUR","").replace(".",",");
sheet.getRange(i,1).setValue(removeCur);
}
}
What I did:
Line 5: I removed the +1 in the loop and added on lastRow directly. If you have 1000 rows, you'll save 1000 assignments
Line 6-7: removed declarations in the loop. If you have 1000 rows, you'll save 2000 re-declarations (not sure if it does, but it's best practise anyway)
You could use regex for the replace, so you do it only once, but I think it's slower, so I kept the 2 replaces there
function CopyinData_AM_A() {
/* Edit the vars below this line for your needs */
var sourceSheet = "Students AM" ; // Enter the name of the sheet with the source data
var sourceRange = "B7:N77" ; // Enter the range of the cells with the source data
var targetSheet = "Students AM A" ; // Enter the name of the target sheet
var targetRange = "B7:N77" ; // Enter the range of cells you wish to copy data to. Note this must be same size as source range.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(sourceSheet);
var values = sheet.getRange(sourceRange).getValues();
ss.getSheetByName(targetSheet).getRange(targetRange).setValues(values);
}
How would I add this into the above script? The action needs to occur after the data is copied into the sheet. I am only a VB coder. 'Students AM A' is a formula free sheet.
For each cell in ‘Students AM A'!N7:N77 then
If Ncell= ‘Menu!D14' then Hcell = ”O"
Check next cell
End
I'd have zero issue doing this in VB, I just started using google scripts about 3 months ago. Anyone recommend a good book to assist me in learning this google script stuff?
Once you have a value range, you can loop through the rows and compare the values against the reference value
You can use e.g. a for loop, a conditonal if statement and the == comparison operator
Sample to be merged with the rest of your function:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName("Students AM");
var referenceSheet = ss.getSheetByName("Menu");
var referenceValue = referenceSheet.getRange("D14").getValue();
var sourceRange = "B7:N77";
var values = sourceSheet.getRange(sourceRange).getValues();
for (var i = 0; i < values.length; i++){
// values[i][12] correspond to the values in column N of each row
if(values[i][12] == referenceValue){
sourceSheet.getRange((sourceRange + i), 7).setValue("O");
}
}
For further reading:
getSheetByName()
getRange(a1Notation)
getRange(row, column)
getValue()
setValue()
first: I really tried hard to get along, but I am more a supporter than a programmer.
I put some Text in Google Calc and wanted to check the amount of the occurances of "Mueller, Klaus" (It appears 5 times within the data range). The sheet contains 941 rows and 1 Column ("A").
Here is my code to find out:
function countKlaus() {
// Aktives Spreadsheet auswählen
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Aktives Tabellenblatt auswählen
var sheet = ss.getSheetByName("Tabellenblatt1");
var start = 1;
var end = sheet.getLastRow();
var data = sheet.getRange(start,1,end,1).getValues();
var curRow = start;
var cntKlaus = 0;
for( x in data )
{
var value = daten[x];
//ui.alert(value);
if(value.indexOf("Mueller, Klaus")> -1){
cntKlaus = cntKlaus + 1;
}
}
ui.alert(cntKlaus);
}
The result message is "0" but should be "5".
Issues:
You are very close to the solution, except for these two issues:
daten[x] should be replaced by data[x].
ui.alert(cntKlaus) should be replaced by SpreadsheetApp.getUi().alert(cntKlaus).
Solution (optimized by me) - Recommended:
function countKlaus() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Tabellenblatt1");
const cntKlaus = sheet
.getRange('A1:A' + sheet.getLastRow())
.getValues()
.flat()
.filter(r=>r.includes("Mueller, Klaus"))
.length;
SpreadsheetApp.getUi().alert(cntKlaus);
}
You can leave out this term + sheet.getLastRow() since we are filtering on a non-blank value. But I think it will be faster to have less data to use filter on in the first place.
References:
flat : convert the 2D array to 1D array.
filter : filter only on "Mueller, Klaus".
Array.prototype.length: get the length of the filtered data
which is the desired result.
includes: check if Mueller, Klaus is included in the text.
Bonus info
Just for your information, my solution can be rewritten in one line of code if that's important to you:
SpreadsheetApp.getUi().alert(SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Tabellenblatt1").getRange('A1:A').getValues().flat().filter(r=>r.includes("Mueller, Klaus")).length);
A novice on app scripts, but managed to successfully build my own script through much research, but now my script is running into errors. Below is my current code:
function MyFunction() {
var sss = SpreadsheetApp.getActive();
var ss = sss.getSheetByName('Daily Sales');
var range = ss.getRange('B8:H83');
var data = range.getValues();
var OUrange = ss.getRange('K8:Q83');
var OUdata = OUrange.getValues();
var ts = sss.getSheetByName('Tracker');
ts.getRange(ts.getLastRow()+1, 1,data.length, data[0].length).setValues(data);
ts.getRange(ts.getLastRow()+1, 1,OUdata.length, OUdata[0].length).setValues(OUdata);
}
In the Daily Sales sheet I am copying values from columns B-H and K-Q and pasting them in the last row of the Tracker sheet starting at Column A. The Daily Sales values in Col. K-Q are pasted correctly below the B-H values, so happy with those results.
On the Tracker sheet these values are in Columns A-G. However I have since added formulas in Columns I and J based on the script data and a manual entry in Column H. These formulas are copied down the entire column within the sheet (i.e. row 5000). Since adding these formulas, the script is now pasting values in A5001.
I realize it is because of these formulas, but is there a way to update my script to paste in the last row of column A while maintaining those formulas in Columns I and J? Thanks in advance.
You could create a helper function that computes the first free row of a given column (NOT a column with formulas)
function MyFunction() {
var sss = SpreadsheetApp.getActive();
var ss = sss.getSheetByName('Daily Sales');
var range = ss.getRange('B8:H83');
var data = range.getValues();
var OUrange = ss.getRange('K8:Q83');
var OUdata = OUrange.getValues();
var ts = sss.getSheetByName('Tracker');
var values = data.concat(OUdata).filter(function (r) {return r[0]});
var fr = findFirstFreeRow(ts, 1) // second parameter represents column to check for first free row
ts.getRange(fr, 1,values.length, values[0].length).setValues(values);
}
function findFirstFreeRow(sheet, colNum) {
var v = sheet.getRange(1, colNum, sheet.getLastRow()).getValues(),
l = v.length,
r;
while (l >= 0) {
if (v[l] && v[l][0].toString().length > 0) {
r = (l + 2);
break;
} else {
l--;
}
}
return r;
}
See if that helps?
After deleting the extra empty rows from your 'Tracker' sheet, you can use the appendRow function to create a new row with the manual values (leaving the cells that need formulas blank).
After the manual values are in, you can then get the cells that need formulas and use setFormula on them.
For the sake of brevity, if you wanted column A,B,C,E,F to have manual values and column D to have a formula you would do:
var sheet = SpreadsheetApp.getActiveSheet();
sheet.appendRow(['A','B','C','','E','F']);
sheet.getRange(sheet.getLastRow(),4).setFormula('=FORMULA(HERE)');
I'd appreciate some help on my first google script if possible.
I'm trying to archive our daily processing log by copying and pasting them to the bottom of an end of day log spreadsheet.
The issue is that the number of rows processed each day will vary so I can't set a fixed number of rows to copy to the target spreadsheet.
I just want to copy from row B7 down to the last row with values entered.
function pasteprocessinglog() {
var sss = SpreadsheetApp.getActiveSpreadsheet();
var ss = sss.getSheetByName('Sheet_name');
var range = ss.getRange('B7:J');
var data = range.getValues();
var tss = SpreadsheetApp.openById('URL');
var ts = tss.getSheetByName('Processing Log');
ts.getRange(ts.getLastRow() + 1, 2, ss.getMaxRows(), 9).setValues(data);
}
I'm currently receiving error: Incorrect range height, was 6467 but should be 6473
I'm guessing this is because it's trying to copy the empty rows too and the spreadsheet isn't long enough.
Any help would be appreciated :)
Thank you!
In data retrieved using ss.getRange('B7:J').getValues(), empty cells are included. So the length of retrieved data is larger than that of real data. 6467 and 6473 means the length of data array and the value from getMaxRows(), respectively.
And getMaxRows() retrieves the number of most bottom cell including empty cells. So in the case for using setValues(), data can be copied by using the length of data array for setValues as numRows of getRange (https://developers.google.com/apps-script/reference/spreadsheet/sheet#getRange(Integer,Integer,Integer,Integer)
).
The script is as follows.
function pasteprocessinglog() {
var sss = SpreadsheetApp.getActiveSpreadsheet();
var ss = sss.getSheetByName('Sheet_name');
var firstrow = 7; // 7th row
var range = ss.getRange(firstrow, 2, ss.getLastRow() - firstrow + 1, 9);
var data = range.getValues();
var tss = SpreadsheetApp.openById('URL');
var ts = tss.getSheetByName('Processing Log');
ts.getRange(ts.getLastRow() + 1, 2, data.length, 9).setValues(data);
}
If my understanding have mistaken, I'm sorry.