Google Script Optimization - google-apps-script

I have a sheet in which IDs are saved. Now occasionally I need to read these IDs and check if other values(Name) in the sheet still fits the ID. The code I have is:
var name = sheet.getRange(line,row).getValue();
var id = sheet.getRange(line,10).getValue();
if(name!== "" && id === "")
{
try
{ get an ID}
Now from what I read I know that calling each cell value seperately takes way more time. However I am not sure how to apply getValues to have these cases fixed.
Basically the same problem in a different dress I have with the following code:
var id = sheet.getRange(line,row).getValue();
if(id!== "" && id!="Not Found" && id!="Not Found old")
{
var url = "some api url "+id+"api key";
try
{
var str = UrlFetchApp.fetch(url).getContentText();
So how do I use get values to check every ID I get. I guess I would have to use some sort of
foreach(id)
or a
for(i= 0; i <= id.range; i++)
{
use id[i] to blablabal
}
But I have no idea how to implement it, any ideas?
Is there maybe even a different, more efficient way?

After calling getValues you get a double array, like [[valueA1, valueB1], [value A2, value B2]]. Process it in a for loop, which may be a double loop if all row/column combinations are involved. Keep in mind that spreadsheet uses 1-bases indexing but in JavaScript they are 0-based. Often, the top row is headers, so it's omitted from the loop below.
function processData() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getDataRange(); // all of data. could be some part of it
var values = range.getValues();
for (var i = 1; i < values.length; i++) { // skipping with header row
for (var j = 0; j < values[0].length; j++) {
if (condition) {
// do something to values[i][j];
}
}
}
range.setValues(values); // put updated values back
}
If the data of interest is in, say, column D, then you can decide to fetch only that column. For example:
function fetchStuff() {
var sheet = SpreadsheetApp.getActiveSheet();
var lastRow = sheet.getLastRow();
var range = sheet.getRange(1, 4, lastRow, 1); // all of column D, note 1-based indexing
var values = range.getValues();
for (var i = 1; i < values.length; i++) { // skipping header row
if (condition) {
var str = UrlFetchApp.fetch(values[i][0]).getContentText();
// do something
}
}
}
Keep in mind that fetch is by far the slowest operation here, and that it is subject to quotas. Use Utilities.sleep(ms) to avoid invoking services too often, and keep track of how much data you are asking the script to fetch.

Related

Google Script For Loop Issues

So this question seems to be beaten to death on the boards, but with all my reading and googling, I just can't figure out what I'm doing wrong.
I'm trying to adapt the code from this link
How to loop a google spreadsheet column values and set result in column B?
Below is what I've adapted it to
function EquationIterationTest(){
var s = SpreadsheetApp.getActiveSpreadsheet();
var sht = s.getSheetByName('Heath, OH')
var drng = sht.getDataRange();
var rng = sht.getRange(13, 2, 111, 1)
//.getRange(13, 2, drng.getLastRow()-1, drng.getLastColumn())
var rngA = rng.getValues();//Array of input values
Logger.log(rngA);
for(var i = 0; i < rngA.length; i++) {
if(rngA[i][0] === 'subtotal'){
rng.offset(0,3).setFormula('=iferror(sum(filter(Invoices!$E:$E,Invoices!$F:$F=$B14,Invoices!$A:$A=$C$2)))');
}
else{
rng.offset(0,3).setValue('Dumb');
}
}
}
When I run this, rngA does get the first column of values (which in this instance starts at B13) however, it will not input the formula in the third column of values. Instead it moves right through the first if statement and executes the else statement. The only thing I can think is there's something wrong either with my if statement or my array.
I tried setting if(rngA[i][0] === 'subtotal') to if(rngA[i][1] === 'subtotal'), but that still returned "dumb" on every line.
Any help would be appreciated so I can stop being "dumb"!
Here's the link to my sheet.
https://docs.google.com/spreadsheets/d/1cDkwWThXDTssH89gJX7W1zKzsW86oLXO-FPfAIJvc-g/edit?usp=sharing
Thanks
The problem is not in your if condition, although if you use three equal signs you are making a strict comparison, so subtotal in your case should start with capital letter.
That said, your problem is happening when you assign a value or formula to rng.offset(0,3), because the result of that expression is a range with the same size as rng but offset 3 columns to the right. You can verify this by using: Logger.log(rng.offset(0,3).getA1Notation());, thus whenever you assign a value or formula there you are assigning it to the whole offset rng. Not what you want right?
You should use offset() from a single cell in your case, not a whole range.
Your function could be simplified to something like the following:
function EquationIterationTest(){
var s = SpreadsheetApp.getActiveSpreadsheet();
var sht = s.getSheetByName('Heath, OH')
var rng = sht.getRange(13, 2, 111, 1)
for(var i = 1; i <= rng.getNumRows(); i++) {
var cell = rng.getCell(i,1);
if(cell.getValue() === 'Subtotal'){
cell.offset(0,3).setFormula(
'=iferror(sum(filter(Invoices!$E:$E,Invoices!$F:$F=$B14,Invoices!$A:$A=$C$2)))'
);
}
else{
cell.offset(0,3).setValue('Dummy');
}
}
}
So I want to make sure I put this here because while the answer above was previously correct, I figured out a much faster way to do it using arrays. This function checks against the data range for a value (in this case "Subtotal") and then appends the equation to rows that that do not contain that value. It is easy to make it compare against the value though by changing the operator from != to ==.
function NewIterationTest(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var activeSheet = ss.getActiveSheet();
var data = activeSheet.getRange(13, 2, 112, 1).getValues();
for (var i = 0; i < data.length; i++){
var rowData = data[i];
var checkData = data;
var row = checkData[i];
var colB = row[0];
if(colB != 'Subtotal'{
activeSheet.getRange(13 + i, 5).setFormula('=iferror(sum(filter(Invoices!$E:$E,Invoices!$F:$F=$B14,Invoices!$A:$A=$C$2)))');
}
}
}
However, if anyone could tell me how to also eliminate compare against whether or not a the text is bold, that would be helpful. Not sure it can be done though since it's pulling against the array.

Moving partial data from one row to another row in same sheet by inserting new row just below

Basically what I am looking for is if I have data in column 9 of each row, I want to split that row into two rows. I do not want to scroll through to see the entire data. If column 9 has data, then cut the data from column 9 to the last column and insert a row below to paste that cut data. I have written a piece of code, but it isn't working.
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var rangeData = ss.getDataRange();
var lastColumn = rangeData.getLastColumn();
var lastRow = rangeData.getLastRow();
var data = ss.getRange(2, 1, lastRow, lastColumn).getValues();
var getRow = rangeData.getRow()
for (var i = 0; i <= lastRow; i++) {
var row = data[i];
Logger.log('data row ' + row);
var rangeToMove = ss.getRange(/*startRow*/ i, /*startColumn*/ 9, /*numRows*/ 1, /*numColumns*/ ss.getMaxColumns());
ss.insertRowAfter(i);
rangeToMove.moveTo(ss.getRange(i++,2,2,9));
}
}
Any help will be much appreciated.
I understand that you want to split the rows in nine columns chunks, writing the row surplus immediately under the original row. If my understanding is correct, the following code will work for your scenario. I saw in your code that you don't want to interact with the first row; I respected that decision in my code.
function so61670015() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var dataRange = sheet.getDataRange();
var lastColumn = dataRange.getLastColumn();
var lastRow = dataRange.getLastRow();
var rawData = sheet.getRange(2, 1, lastRow - 1, lastColumn).getValues();
var maxColumn = 9; // Column I
var data = [];
// Step I - Clean
for (var j = 0; j < rawData.length; j++) {
data.push(rawData[j].filter(value => value != ""));
}
// Step II - Wrap
for (var j = 0; j < data.length; j++) {
if (data[j].length > maxColumn) {
var surplus = data[j].slice(9);
data[j].splice(9);
data.splice(j + 1, 0, surplus);
}
}
// Step III - Fill
for (var j = 0; j < data.length; j++) {
if (data[j].length < maxColumn) {
var emptyColumns = maxColumn - data[j].length;
for (var s = 0; s < emptyColumns; s++) {
data[j].push("");
}
}
}
// Step IV - Overwrite
sheet.getRange(2, 1, lastRow, lastColumn).setValue("");
sheet.getRange(2, 1, data.length, 9).setValues(data);
}
I commented on the different sections of the code to make it clearer. In the initial step I declared all the variables that will be needed later. This part of the code uses the same Spreadsheet methods as your original code.
In the first step the code will clean all the void values of the data (void as in "", not null). This will help to differentiate empty cells from filled ones. I used the method .filter() to write in the array data only the non-void values.
Then the code will cut every row at the maximum column size (column I in your question) and write the row surplus in a new row below. The code performs that action with the methods .slice() and .splice() (to read and modify the array respectively). Please, note how I use .splice() twice: first to modify the row by removing every cell over the column limit, and later to modify the data array to insert a row with the surplus cells. If the surplus is still longer than the maximum allowed, then it will repeat the operation until all rows adhere to the column limit.
After those procedures, the data need to be filled again with void values (as in "") in those rows that ended up being smaller than the column limit. This action will be required to write the data later, because the method .setValues() requires the array of values being of equal size of the selected range. To accomplish that I used the .push() method to insert void values in every row until the column limit is reached.
Finally the data is ready to be written. In a two steps process, the code will first clear the original sheet and write the final data immediately after. This marks the end of the operation.
To illustrate the usage of this code, if we use the following sample data:
We will end up with this sheet:
Please, ask me any question if you need further help or clarifications.

Copy active cell to other cells containing string

In Google Sheets I'm trying to create a script that will take the value from the active cell and paste that value to any cell in Column B containing the string "HR". Any ideas?
This isn't too bad; you just have to wrap your head around a few concepts from Apps Script and Javascript to make it efficient. But first let's start with the naive approach!
function firstTry() {
var activeSheet = SpreadsheetApp.getActiveSheet(); // whatever is open
var activeCell = SpreadsheetApp.getCurrentCell(); // this is a single-cell range
var activeCellValue = activeCell.getValue(); // could be a string, number, etc
// Now let's look in column B for stuff to change
for (var i = 1; i <= activeSheet.getLastRow(); i++) {
var cell = activeSheet.getRange("B" + i);
var val = cell.getValue();
var valStr = String(val); // We could have gotten a number
if (valStr.indexOf("HR") != -1) {
cell.setValue(activeCellValue);
}
}
}
This will probably work, but isn't too efficient: each call to getValue() or setValue() takes some time. It'd be better to just get all the values at once, and then paste back a modified Column B when we're satisfied:
function improvement() {
var activeSheet = SpreadsheetApp.getActiveSheet(); // whatever is open
var activeCell = SpreadsheetApp.getCurrentCell(); // this is a single-cell range
var activeCellValue = activeCell.getValue(); // could be a string, number, etc
// Now let's look in column B for stuff to change
var rowsWithData = activeSheet.getLastRow() - 1;
var colBRange = activeSheet.getRange(1, // start on row 1
2, // start on column 2
rowsWithData, // this many rows
1); // just one column
// Let's get the data as an array of arrays. JS arrays are 0-based, btw
var colBData = colBRange.getValues();
for (var i = 0; i < colBData.length; i++) {
var val = colBData[i][0]; // row i, first column
var valStr = String(val); // We might have gotten a number
if (valStr.indexOf("HR") != -1) {
colBData[i][0] = activeCellValue; // modify copied data
}
}
// Lastly, write column B back out
colBRange.setValues(colBData);
}
You could go further with a fancy filter function instead of looping over the data explicitly, but that starts to get less clear.
Caveats as the OP points out in comments below, blindly calling setValues like this will pave over any formulas you have. This would have been no big deal, except that this includes hyperlinks. You could get really involved by calling getFormulas in parallel with getValues and then decide whether to call setValue or setFormula depending on the original contents of each cell.

Google App Script - loops not executing

I want to create a function that will iterate over every sheet until a given sheet. The function receives the name of that last sheet as an argument.
function getUntilMonthSavings(month) {
var spreadsheet = SpreadsheetApp.getActive();
var monthSheet = spreadsheet.getSheetByName(month);
var allSheets = spreadsheet.getSheets();
var sheetNumber = monthSheet.getIndex();
var totalSavings=0;
for (var i = 1; i < monthSheet; i++){
totalSavings = totalSavings + allSheets[i].getRange("I20").getValue();
}
return totalSavings;
}
My problem is that what is returned is always 0. I've also returned i to check if it is being iterated, but it returns 1 even when the sheet index is greater than 1.
I'm sure to be doing some kind of basic blunder, but I'm quite at a loss as why this code is not working.
MonthSheet is an object and not something you can compare i to in your loop. You need the actual index of the sheet referred to.
Try:
for (var i = 0; i < monthSheet.getIndex(); i += 1) {

testing for background color in google script

Just a note: I am not very versed in coding and brand new to google script.
I am trying test for background color within a script. Specifically, I will have an array of names stored into a named range and want to count how many cells are set to green.
So far I have the following but receive an error: TypeError: Cannot set property "0.0" of undefined to "#00ff00"
function testCount(range) {
var ranges = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("testrange");
var names = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("testrange").getValues();
var NumColumns = ranges.getNumColumns();
var NumRows = ranges.getNumRows();
var c = 0;
for (var i = 0; i<NumColumns; i++){
for (var j = 0; j<NumRows; j++){
if (ranges.getBackgrounds()[i][j] ="#00ff00"){
c++;
}else{
c=c;
}
}
}
return c;
I grabbed the value for green when I tried the following for a cell that was colored
return ranges.getBackgrounds()[0][1];
Just looks like your code needs a little cleaning. I'll explain the edits.
function testCount() {
var ranges = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("testrange");
No need to have the var names line because it seems that you don't use it.
var NumColumns = ranges.getNumColumns();
var NumRows = ranges.getNumRows();
Grab the backgrounds of all the cells at once and store that in a variable.
var backgrounds = ranges.getBackgrounds();
var c = 0;
for (var i = 0; i<NumColumns; i++){
for (var j = 0; j<NumRows; j++){
Reference the backgrounds variable that we created above. Also, the first number is the row number, and the second number is the column number. So you'll want to swap i and j from what you had originally. Also, a = 10 assigns the value of 10 to the variable a. To check for equality, you use ==. This checks if the two values are the same.
if (backgrounds[j][i] == "#00ff00"){
c++;
}
No need to have an else statement that doesn't do anything. You can leave the else part out.
}
}
return c;
}