Using setvalues with a for loop - arrays

I am currently trying to perform a "copyTo" or "setValues" action.
I need to copy one grid (Sheet1!J16:Q36) to another grid (Sheet2!J16:Q36)
But not every cell shall be copied. Only those values that are not identical to the Sheet1 values shall be copied.
I have tried the below code with success, but sadly the script takes ages.
I understand that a batch operation with getValues in an array will be quicker, but I lack the capability to do that script.
I also used a third grid which compared the values of sheet1 and 2 and returned 1 or 0. Only if the value 1 was shown, the cell was considered by the for loop. I take it that this is inefficient.
Thank you for your help. I appreciate it a lot.
var ratenprogramm = SpreadsheetApp.getActiveSpreadsheet();
var ratenprogrammmain = ratenprogramm.getSheetByName("Ratenprogramm");
var vorlageratenprogramm =
ratenprogramm.getSheetByName("VorlageRatenprogramm");
for(i=1;i<=21;i++)
{
for(j=1;j<=8;j++)
{
if(vorlageratenprogramm.getRange(37+i,9+j).getValue() == 1)
{
vorlageratenprogramm.getRange(15+i,9+j).copyTo(ratenprogrammmain.getRange(15+i,9+j),{contentsOnly: true});
}
}
}

As you have noticed, calling any external services, including methods
like getValue() make your script slow, see Apps Script Best
Practices.
Your code can be optimized by replacing the multiple getValue()
requests by a single getValues().
Within the nested loops you can specify a multiple amount of ranges
and values that can be written with the Advanced Sheets Service,
with the Sheets API method spreadsheets.values.batchUpdate into
the corresponding ranges of the destination sheet, see also
here.
Sample
function myFunction() {
var ratenprogramm = SpreadsheetApp.getActiveSpreadsheet();
var ratenprogrammmain = ratenprogramm.getSheetByName("Ratenprogramm");
var vorlageratenprogramm = ratenprogramm.getSheetByName("VorlageRatenprogramm");
var data=[];
var range=vorlageratenprogramm.getRange(15,9,21,8);
var values=range.getValues();
for(i=0;i<4;i++)
{
for(j=0;j<1;j++)
{
if(values[i+1][j] == 1)
{
var cell=range.getCell(i+1,j+1).getA1Notation();
data.push([{ range:'Ratenprogramm!'+ cell, values: [[values[i+1][j]]]}]);
}
}
}
var resource = {
valueInputOption: "USER_ENTERED",
data: data
};
Sheets.Spreadsheets.Values.batchUpdate(resource, spreadsheetId);
}
Keep in mind that if you have many different ranges, it might be
easier and faster to overwrite the sheet with the complete range,
rather than using nesting looping. E.g.
vorlageratenprogramm.getRange(15,9,21,8).copyTo(ratenprogrammmain.getRange(15,9,21,8),{contentsOnly:
true});.

Related

AppScript: 'number of columns in the data does not match the number of columns in the range.' setValues method not reading array correctly?

I'm trying to automate the collection of phone numbers from an API into a Google Sheet with app script. I can get the data and place it in an array with the following code:
const options = {
method: 'GET',
headers: {
Authorization: 'Bearer XXXXXXXXXXXXXXX',
Accept: 'Application/JSON',
}
};
var serviceUrl = "dummyurl.com/?params";
var data=UrlFetchApp.fetch(serviceUrl, options);
if(data.getResponseCode() == 200) {
var response = JSON.parse(data.getContentText());
if (response !== null){
var keys = Object.keys(response.call).length;
var phoneArray = [];
for(i = 0; i < keys; i++) {
phoneArray.push(response.call[i].caller.caller_id);
}
This works as expected - it grabs yesterday's caller ID values from a particular marketing campaign from my API. Next, I want to import this data into a column in my spreadsheet. To do this, I use the setValues method like so:
Logger.log(phoneArray);
var arrayWrapper = [];
arrayWrapper.push(phoneArray);
Logger.log(arrayWrapper);
for(i = 0; i < keys; i++) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var cell = sheet.getRange("A8");
cell.setValues(arrayWrapper);
}
}
}
}
I am aware that I need my array length to equal the length of the selected range of cells in my sheet. However, I get conflicting errors depending on the length I set for my getRange method. If I set it to a single cell, as you see above, the error I get is:
The number of columns in the data does not match the number of columns in the range. The data has 8 but the range has 1.
However, if I set the length of my range to 8 (or any value except 1), I get the error:
The number of columns in the data does not match the number of columns in the range. The data has 1 but the range has 8.
As you see, the error swaps values. Now I have the appropriate number of columns in the range, but my script only finds 1 cell of data. When I check the log, I see that my 2D array looks normal in both cases - 8 phone numbers in an array wrapped in another array.
What is causing this error? I cannot find reference to similar errors on SO or elsewhere.
Also, please note that I'm aware this code is a little wonky (weird variables and two for loops where one would do). I've been troubleshooting this for a couple hours and was originally using setValue instead of setValues. While trying to debug it, things got split up and moved around a lot.
The dimension of your range is one row and several columns
If you push an array into another array, the dimension will be [[...],[...],[...]] - i.e. you have one column and multiple rows
What you want instead is one row and multiple columns: [[...,...,...]]
To achieve this you need to create a two-dimensional array and push all entries into the first row of your array: phoneArray[0]=[]; phoneArray[0].push(...);
Sample:
var phoneArray = [];
phoneArray[0]=[];
for(i = 0; i < keys; i++) {
var phoneNumber = response.call[i].caller.caller_id;
phoneNumber = phoneNumber.replace(/-/g,'');
phoneArray[0].push(phoneNumber);
}
var range = sheet.getRange(1,8,1, keys);
range.setValues(phoneArray);
So I figured out how to make this work, though I can't speak to why the error is occurring, or rather why one receives reversed error messages depending on the setRange value.
Rather than pushing the whole list of values from the API to phoneArray, I structured my first for loop to reset the value of phoneArray each loop and push a single value array to my arrayWrapper, like so:
for(i = 0; i < keys; i++) {
var phoneArray = [];
var phoneNumber = response.call[i].caller.caller_id;
phoneNumber = phoneNumber.replace(/-/g,'');
phoneArray.push(phoneNumber);
arrayWrapper.push(phoneArray);
}
Note that I also edited the formatting of the phone numbers to suit my needs, so I pulled each value into a variable to make replacing a character simple. What this new for loop results in is a 2D array like so:
[[1235556789],[0987776543],[0009872345]]
Rather than what I had before, which was like this:
[[1235556789,0987776543,0009872345]]
It would appear that this is how the setValues method wants its data structured, although the documentation suggests otherwise.
Regardless, if anyone were to run into similar issues, this is the gist of what must be done to fix it, or at least the method I found worked. I'm sure there are far more performant and elegant solutions than mine, but I will be dealing with dozens of rows of data, not thousands or millions. Performance isn't a big concern for me.
var correct = [[data],[data]] -
is the data structure that is required for setValues()
therefore
?.setValues(correct)

In a form, I need to populate the contents of same spreadsheet column like 50 times. Is there a way to script this logic with fewer iterations

I'm working on a form where I need to pull the contents of a spreadsheet column like 50 times, to try to input multiple items from a list. I see that I can do this by defining a few variables and redoing a small piece of Script again and again. I want to see if anyone can help me overcome this lengthy script to make it smaller with fewer iterations. Thanks.
function updateForm(){
// call the form and connect to the drop-down items
var Form_SQ = FormApp.openById("FORM ID");
var SQ_IT01_List = Form_SQ.getItemById("ITEM 01").asListItem();
var SQ_IT02_List = Form_SQ.getItemById("ITEM 02").asListItem();
//Similarly defining upto 50 dropdown lists.
var SS01 = SpreadsheetApp.getActive();
var SQ_IT01_Names = SS01.getSheetByName("Sheet2");
var SQ_IT02_Names = SS01.getSheetByName("Sheet2");
//Similarly defining upto 50 names lists.
// Item_01 Part Number Dropdown
var SQ_IT01_Values = SQ_IT01_Names.getRange(2, 1, SQ_IT01_Names.getMaxRows() - 1).getValues();
var SQ_IT01_Items = [];
for(var i = 0; i < SQ_IT01_Values.length; i++)
if(SQ_IT01_Values[i][0] != "")
SQ_IT01_Items[i] = SQ_IT01_Values[i][0];
SQ_IT01_List.setChoiceValues(SQ_IT01_Items);
// Item_02 Part Number Dropdown
var SQ_IT02_Values = SQ_IT01_Names.getRange(2, 1, SQ_IT02_Names.getMaxRows() - 1).getValues();
var SQ_IT02_Items = [];
for(var i = 0; i < SQ_IT02_Values.length; i++)
if(SQ_IT02_Values[i][0] != "")
SQ_IT02_Items[i] = SQ_IT02_Values[i][0];
SQ_IT02_List.setChoiceValues(SQ_IT02_Items);
//Similarly defining upto 50 lookup lists.
}
Problem
Reusing code and making use of loops. Scripting is all about efficiency (see DRY principle): make as little assignments and same-functionality coding as possible - use loops, move reusable code snippets to functions that can be called on demand, etc.
Solution
This sample makes several assumptions:
SQ_IT01_Names is different for each item (in your sample it always is Sheet2 - if this is the case, you don't have to reassign it 50 times, one variable assignment will do just fine).
You intended to do something when a value is an empty string (the sample just filters them out). As you use the [index] notation, those values in the resulting Array will be undefined (and that's not something one would want in an Array of choice values).
All items are choice items (if you need id filtering, the sample is easily expanded).
function updateForm() {
var form = FormApp.openById("FORM ID");
//access every item;
var items = form.getItems();
var ss = SpreadsheetApp.getActive();
//loop over items;
items.forEach(function(item,i){
var namesSheet = ss.getSheetByName('Sheet'+i); //assuming this is diff each time;
var namesRange = namesSheet.getRange(2,1,namesSheet.getLastRow());
var namesValues = namesRange.getValues();
//map values to first column;
namesValues = namesValues.map(function(value){
return value[0];
});
//filter out undefined (undefined and false functional equivalence);
namesValues = namesValues.filter(function(value){
return value;
});
item.asListItem().setChoiceValues(namesValues);
});
}
Notes
Please, use closures {} with loops and if statements, this way you'll be able to keep track of which statements are enclosed in it and save yourself debugging time when looping / conditioning multiple statements.
Since you only need rows that have data in them, use the getLastRow() method instead of the getMaxRows()-1 calc you have to perform in your script.
Reference
forEach() method reference;
filter() method reference;
map() method reference;
getLastRow() method reference;

Node fast way to find in array

I have a Problem.
My script was working fine and fast, when there was only like up to 5000 Objects in my Array.
Now there over 20.000 Objects and it runs slower and slower...
This is how i called it
for(var h in ItemsCases) {
if(itmID == ItemsCases[h].sku) {
With "for" for every object and check where the sku is my itmID, cause i dont want every ItemsCases. Only few of it each time.
But what is the fastest and best way to get the items with the sku i need out of it?
I think mine, is not the fastest...
I get multiple items now with that code
var skus = res.response.cases[x].skus;
for(var j in skus) {
var itmID = skus[j];
for(var h in ItemsCases) {
if(itmID == ItemsCases[h].sku) {
the skus is also an array
ItemsCases.find(item => item.sku === itmID) (or a for loop like yours, depending on the implementation) is the fastest you can do with an array (if you can have multiple items returned, use filter instead of find).
Use a Map or an object lookup if you need to be faster than that. It does need preparation and memory, but if you are searching a lot it may well be worth it. For example, using a Map:
// preparation of the lookup
const ItemsCasesLookup = new Map();
ItemsCases.forEach(item => {
const list = ItemsCasesLookup.get(item.sku);
if (list) {
list.push(item)
} else {
ItemsCasesLookup.set(item.sku, [item]);
}
});
then later you can get all items for the same sku like this:
ItemsCasesLookup.get(itmID);
A compromise (not more memory, but some speedup) can be achieved by pre-sorting your array, then using a binary search on it, which is much faster than linear search you have to do on an unprepared array.

Google Apps Script Replace and update cell within a range

I have a Google spreadsheet that I'm trying to remove the word "woo" within a range of cells
So far I've managed to loop through the results and log the results, however I haven't figured how to update that information in the spreadsheet itself.
Any guidance would be welcomed
Thank you
function myFunction () {
var ss = SpreadsheetApp.getActiveSheet().getRange('B:B')
var data = ss.getValues();
for (var i = 0; i < data.length; i++) {
var text = data[i].toString();
var finaltext = text.replace(/woo/g, "");
data[i] = finaltext;
Logger.log(data[i]);
}
}
Use setValues()
Notes:
Usually ss is used as a shorthand for spreadsheet, as it's used on the code for a range it's better to use range as a variable name.
setValues() returns a 2D array, so data[i] returns an array of row values rather than a cell value. To get/set cell values, use data[i][0] notation.
Considering the above replace
var ss = SpreadsheetApp.getActiveSheet().getRange('B:B')
by
var range = SpreadsheetApp.getActiveSheet().getRange('B:B')
then add the following line after the for block.
range.setValues(data);
Regarding text var declaration, replace
var text = data[i].toString();
to
var text = data[i][0].toString();
Using open ended references like B:B could lead to problems. To avoid them be sure to keep the sheet rows at minimum or better instead of using an open ended reference use something like B1:B10.

Sheet Names stored in array, Google Apps Script only Accessing first two

I have been using Google Apps Script along with spreadsheet to create a spreadsheet that watches a set of stocks. I have assigned each stock it's own sheet and set up the function with a day trigger so that it refreshes all of the information each day. I spent so much time debugging a lot and finally got it working perfectly for the first two sheets. I now added 3 more and it's not doing anything for them.
function XMLDATAONDAY() {
for (r=0;r<5;r++){
var ss=SpreadsheetApp.getActiveSpreadsheet()
var sheets= ["HSY","AAPL","CENX","MSFT","TSLA"]
var sheet=ss.getSheetByName(sheets[r])
var i=14
var dateSrc=sheet.getRange(2,5)
var stockPrice = sheet.getRange(5,4).getValue()
var displayCell= sheet.getRange(2,4)
var date = dateSrc.getValue()
SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/1IOBQpdUr0fq_clie-0AOnCAN87qTy9Yn1h79akMJ7uc/edit#gid=0');
for (i=14;i<366;i++) {
var sheets= ["HSY","AAPL","CENX","MSFT","TSLA"]
var stockCell=sheet.getRange(i,2)
var dateCell=sheet.getRange(i,1)
if(stockCell.getValue()== ""){
sheet.getRange(14,1).copyFormatToRange(sheet, 1, 1, i, i)
sheet.getRange(14,2).copyFormatToRange(sheet, 2, 2, i, i)
dateCell.setValue(date);
stockCell.setValue(stockPrice);
i=400;
}
}
}
}
It's probably something that I'm just overlooking, but I just can't seem to find anything.
This line:
i=400;
Should probably be changed to:
break;
I'm guessing that you are setting i to 400 in order to stop the next loop from running?
There is a duplicate variable assignment for sheets. It's an array literal. Only define that once, and put it outside of the for loops. Put the variable assignment for ss outside of the loop also. If you do not assign r with a statement, the code still runs without any noticeable difference, but without a var statement, the variable is defined in the global scope.
The statement to open a spreadsheet by URL is not doing anything. It's not assigned to a variable, so I'm not sure what that line is for.
function XMLDATAONDAY() {
var date,dateCell,dateSrc,displayCell,i,r,sheet,sheets,ss,stockCell,stockPrice;
sheets = ["HSY","AAPL","CENX","MSFT","TSLA"];
ss=SpreadsheetApp.getActiveSpreadsheet();
r=0;
for (r=0;r<5;r++){
sheet=ss.getSheetByName(sheets[r])
i=14
dateSrc=sheet.getRange(2,5)
stockPrice = sheet.getRange(5,4).getValue()
displayCell = sheet.getRange(2,4)
date = dateSrc.getValue()
//SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/sheet_ID_HERE/edit#gid=0');
for (i=14;i<366;i++) {
stockCell=sheet.getRange(i,2)
dateCell=sheet.getRange(i,1)
if(stockCell.getValue()== ""){
sheet.getRange(14,1).copyFormatToRange(sheet, 1, 1, i, i)
sheet.getRange(14,2).copyFormatToRange(sheet, 2, 2, i, i)
dateCell.setValue(date);
stockCell.setValue(stockPrice);
break;
}
}
}
}

Resources