Issues using setValues function - google-apps-script

I keep getting an error message when using the setValues() function to export an array to Google sheets. I have tried many different methods for creating my 2D array, but I still keep getting the same errors. Sometimes my code will run (the array will export to the spreadsheet) but I will still get an error.
I originally used the setValue() function in a for loop but the code would time out because it ran too long. So I tried dumping all my data into a 2D Array and feeding that to the spreadsheet all at once.
Tried creating the Array as an empty 2D array
var outputArray = [[]]
and using .push to populate the data into it
Tried creating the empty Array using the function:
function create2DArray(rows) {
var arr = [];
for (var i=0;i<rows;i++) {
arr[i] = [];
}
return arr;
}
and adding the data in by rows (inside of a for loop that iterates by rowNumber)
outputArray[rowNumber] = [data1, data2, data3,...]
Used the same function above for creating empty array and created intermediate array and then put that into output array
outputArrayIntermediate[0] = data1;
outputArrayIntermediate[1] = data2;
outputArrayIntermediate[2] = data3;
outputArrayIntermediate[3] = data4;...
outputArray[rowNumber] = outputArrayIntermediate;
Here is where the error keeps happening
var setRows = outputArray.length;
var setColumns = outputArray[0].length
revenueSheet.getRange(2,1,setRows,setColumns).setValues(outputArray);
When I include the setColumns variable I get the error:
"The number of columns in the data does not match the number of columns in the range. The data has 0 but the range has 11."
This will still populate the data to the spreadsheet.
When I do not include the setColumns variable I get the error:
"The number of columns in the data does not match the number of columns in the range. The data has 11 but the range has 1."

Is there ever an instance where one row has more columns than another row? For instance if 'row' 1 in your data as 5 columns (outputArray.length = 5) and row 2 has 6, then the data needs a range with 6 columns.
If this is the case, here are some solutions in order of simplicity:
1. If there is no important data to the right of where you are inserting you data you can use revenueSheet.getMaxColumns() in your .getRange().setValues().
2. Iterate through the data set to find the row with the longest length and set that as the number of columns. To do this see this answer for a few options.

You only need [] not [[]], because you are pushing arrays into outputArray, and push makes sure it will always expand when needed.
Try this:
outputArray = [];
outputArray.push([data1, data2, data3]);
outputArray.push([data4, data5, data6]);
revenueSheet.getRange(2,1,outputArray.length,outputArray[0].length).setValues(outputArray);

Perhaps this will help you:
function createSSData(numrows,numcols) {
var numrows=numrows || 20;
var numcols=numcols || 20;
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var values=[];
for(var i=0;i<numrows;i++) {
values[i]=[];
for(var j=0;j<numcols;j++) {
values[i].push(Utilities.formatString('row: %s,col: %s',i+1, j+1));
}
}
sh.getRange(1,1,values.length,values[0].length).setValues(values);
}

Related

Copy data range and paste to new page, and repeat

I am trying to copy a range from sheet 'Full' and paste the values only to a new sheet, 'Dump'. While the macro below does its action once, I am regenerating the original data range (Full), so I want to copy that new set and append to the same output page, indexed down to a blank row and keeping the first pasted data. Also then to do this 100 times.
The recoded macro is below, and I need to understand the script to add in to;
repeat the copy/paste function 100 times, and also
offset the paste range by a set number of rows.
Sorry, genuine newbie at editing google sheet macros. The Excel macro I use doesn't translate over.
Appreciate any answers you have.
function xmacro() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A1').activate();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Full'), true);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Dump'), true);
spreadsheet.getRange('Full!BK3:BT34').copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);```
};
Your macro is just an automatically generated app script. You can extend its functionality by building off that with some more code. First I'll explain some of the basic concepts, if you know this, then just skip down to the code.
Sheets Concepts
Here are some basic concepts that took me forever to figure out because most of the documentation assumes you are already proficient at Javascript.
A range is a 2 dimensional array that has one array for each row, and the contents of that array are the columns:
someRange = [
[row1Col1, row1Col2, row1Col3, row1Col4],
[row2Col1, row2Col2, row2Col3, row2Col4],
[row3Col1, row3Col2, row3Col3, row3Col4]
]
To access a specific value you need to reference the row array, and then the index of the column you want.
Think about it like hotel room numbers. The first part of the number is the floor,
and the second part is the specific room on that floor.
You access arrays by calling the array name, then square brackets with the index number of the element you want.
Arrays are indexed starting at 0, so to get row 1 you would use:
someRange[0] would return the inner array [row1Col1, row1Col2, row1Col3].
But that doesn't give you a specific cell values - so you would use a second set of brackets to access the column in that row:
someRange[0][1] = 'row1Col2'
Arrays also have built in information, so you can find the length of an array by using Array.length no parenthesis.
Since the rows are in the outer array, you can get the number of rows by seeing how many inner arrays there are.
someRange.length = 3 There are 3 row arrays in the someRange array.
You can do the same with columns, since the number of columns is equal to the number of elements in an array. To get the number of elements in the first row you would use:
someRange[0].length - which would be 4
And since a range has the same number of columns for each row, you can pick any row
to get the number of columns (generally, there are always exceptions)
The Code
The first function will create a custom menu item to run the code.
// create a new menu item for your custom function
function onOpen(){
SpreadsheetApp.getUi().createMenu()
.addItem('100 Copies', 'lotsOfCopies')
.addToUi();
}
function lotsOfCopies() {
var ss = SpreadsheetApp.getActive();
var copySheet = ss.getSheetByName('yourCopySheetName');
var pasteSheet = ss.getSheetByName('yourPasteSheetName');
// the range you wish to copy, change to fit your needs
var copyRange = copySheet.getRange('A1:B7');
var copyValues = copyRange.getValues();
var copyRows = copyValues.length;
var copyCols = copyValues[0].length;
// define the first row to be pasted into
var pasteRow = 1;
// define the left side column of the range to be pasted into
var pasteCol = 1
// build a loop that does the same thing 100 times,
// and each time offsets the paste range by the number of rows in the copy range
for (var i = 0; i < 100; i++) {
// for every iteration after the first,
// add the number of rows in the copy range to the variable 'row'
// example if there are 10 rows in the copy range then
// iteration 1 row = 1 Iterartion 2 row = 11, Iteration 3 row = 21
if (i > 0) {
pasteRow = +pasteRow + +copyRows
}
// build the range to paste into - it starts on pasteRow and paste col,
// and is as many rows as the copied range, and as many columns as the copied range
let pasteRange = pasteSheet.getRange(pasteRow, pasteCol, copyRows, copyCols);
// put the values from copyValues into the pasteRange
pasteRange.setValues(copyValues);
}
}
function xmacro() {
const ss = SpreadsheetApp.getActive();
const ssh = ss.getSheetByName('Full')
const dsh = ss.getSheetByName('Dump')
ssh.getRange('BK3:BT34').copyTo(dsh.getRange('A1'), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}

Find rows with particular value from a Google Spreadsheet without using loop?

I am new to spreadsheet scripting. I am generating a report (new sheet) based on another sheet where I enter values daily. In Apps Script I first generate the sheet then loop through the data range retrieved from that input sheet.
After that I have to merge values based on dates and categories.
Now my report format is such that rows are categories and dates are columns.
So if in input if there is another value with same date and same category I have to add the value.
My problem is how to check if the value with same date and category exists in the report and I DO NOT want to use loops as I am already in loops so that will make the process run very very slow.
I don't think it is possible to do it without some looping. Since this operation is carried out server side without the need to make calls to the spreadsheet it would take a very small amount of time even with a very large dataset.
If your script is already slow it more than likely because of inefficiencies/ delays in some other part of the script. I have a script which duplicates a spreadsheet and renames it, just those to operations take between 5 & 8 seconds.
As an example:
function test(){
var ss = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
var value = "agdsdfgsdfg" // This value is in cell BD1000
for(var i = 0; i < ss.length; i ++){
if(ss[i].indexOf(value)>=0){
var x = ss[i].indexOf(value) + 1;
break;
}
}
var y = i + 1
// var x & y are the row, cell coordinates of value in the data range
}
This operation carried out on a dataset 56 columns x 1000 rows completes in 0.88 seconds with the search value in the last cell of the range.
Your report sounds a fair bit like a Pivot Table with categories in rows, dates in columns, and SUM(Value) as the data field. To reproduce this with a script report, you can use an Object variable that maps between a key and a "value" Object:
This probably isn't your exact use case (it assumes you need to generate a new report from a possibly-large stack of feeder data, but it should demonstrate how you can use nested Objects to simplify / internalize the lookup process, including testing for undefined values and enforcing a rectangular output.
// Make an array of the data, to limit use of the slow spreadsheet interface.
var inputs = SpreadsheetApp.openById(<id>).getSheetByName(<dataSheetName>)
.getDataRange().getValues();
// The first row is probably column headers from the input sheet, and
// doesn't likely contain useful data that you want in your report.
var headers = inputs.splice(0, 1);
var report = {};
for(var row = 0; row < inputs.length; ++row) {
// Change these indexes (0, 1, 2) to the proper values.
// Also do any necessary formatting / validation, etc. for "category" and "date".
var category = inputs[row][0];
var date = inputs[row][1];
var value = inputs[row][2];
// If this category doesn't exist, default construct its report object.
// For each category, a nested object is used to store the date-value pair.
if(!report[category]) {
report[category] = {};
}
// Otherwise, if the date is not yet seen for the category, set
// the value. If it is seen, increment the stored value by the new value.
if(!report[category][date]) {
report[category][date] = value;
} else {
// Treat this as numeric addition, not string concatenation.
report[category][date] += value - 0;
}
}
// To print your report, you need a header you can index against.
var outputHeader = [];
for(var category in report) {
for(var date in category) {
outputHeader.push(date);
}
}
// Sort this header row. If the dates are strings that don't simply
// coerce to proper Date objects, you'll need to write your own sort() method.
// (You don't technically need to sort, but if you don't then the dates
// won't be "in order" when the report prints.)
outputHeader.sort();
// After sorting, add a row label for the header of sorted dates.
outputHeader.splice(0, 0, "Category / Date");
// Serialize the report object into an array[][];
var output = [outputHeader];
var totalColumns = outputHeader.length;
for(var category in report) {
// Initialize each row with the row label in the 0 index position.
var row = [category];
for(var date in category) {
var index = outputHeader.indexOf(date);
row[index] = category[date];
}
// Unless you are guaranteed that every category has a value for every date
// in the report, you need to ensure that the row has a value at each index.
// (This is a good idea anyway, to ensure that you have a rectangular array.)
var filled = Object.keys(row);
// We can start at 1 since we know that every row starts with its category.
for(var col = 1; col < totalColumns; ++col) {
if(filled.indexOf(String(col)) < 0) {
row[col] = "";
}
}
output.push(row);
}
SpreadsheetApp.openById(<id>).getSheetByName(<reportSheetName>)
.getRange(1, 1, output.length, output[0].length).setValues(output);

A google script for suming two columns from spreadsheet and display back to spreadsheet

I wrote a google script to sum two columns of max 50 numbers in a google spreadsheet. however, I cannot get a correct result. The codes are attached below.
function DailyCreditCalculation() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
var values_old = sheet.getSheetValues(7,2,50,1);
var values_new = sheet.getSheetValues(7,3,50,1);
for(i = 0; i <= values_new.length-1; i++){
values_old[i] = +values_old[i];
values_new[i] = +values_new[i];
values_old[i] = values_old[i] + values_new[i];
}
var cell = sheet.getRange(7,2,50,1);
cell.setValues(values_old);
}
When I run the script, it shows error that
"Cannot convert Array to Object[][]"
How can I modify the script so that it can run successfully?
The data you are retrieving are two column Vectors which are represented as arrays of arrays for consistency, e.g.
[[1],
[2],
[3]]
which is why you have to do the +values_old[i] conversion.
The output of your loop makes the result an Array as specified in the error message so if you have two identical arrays the result will be e.g. [2,4,6].
setValues needs an array of array to write data (Object[][]).
Since you want it to be a column vector you can say values_old[i] = [values_old[i] + values_new[i]]; which will make the result
[[2],
[4],
[6]]
a format setValues can work with.

How to use the getRange() method with an array value as one of the parameters

I don't have much experience using Javascript but I'm developing a simple code to filter some information relevant to a professor I'm helping. I am searching the row number of a certain amount of data using a for and then I'm using an array to store all the rows that contain those words. Since I'm using Appscript, I only need to relocate a certain amount of data from the row I'm returning to a final row I've already know. My code is as follows:
if(cell === "Average")
{
index++;
initialcoords[index] = n; // n is the iteration variable in the for
}
I've tested the contents of the array and they are just fine, so I'm storing correctly the rows. The problem is that I'm using a different method to paste the data in a different sheet in Google Spreadhsheets. My code to do so is the following:
function pasteInfo()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheetByName("Sheet 1");
var destination = ss.getSheetByName("Sheet 2");
var range = source.getRange(initialcoords[1], 1, 8, 3);
range.copyValuesToRange(destination, 4, 6, 4, 6);
}
My probelm is the getRange() since it prints an error like this:
can't find method getRange((class),number,number,number).
I believe that even if n is declared as an integer, the values that I'm returning are of a different type incompatible with the getRange() method. Could anyone help me to confirm this and to help me convert it to integer? I would really appreciate your help.
You first need to define the Sheet you want to get the data from since a Spreadsheet can have multiple Sheets.
You need to ensure you have appropriate default values defined before using the parameters, otherwise the interpreter will start making guess.
Provide defaults if parameters are empty:
function fillLine(row, column, length, bgcolor)
{
row = row || 0;
column = column || 0;
length = length || 1;
bgcolor = bgcolor || "red";
var sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange(1+row, 1+column, 1, length).setBackground(bgcolor)
}
You may also try the solution offered by community: Can't get Google Scripts working

exceeded maximum execution time in google-apps-script

I have two spreadsheets. I want to match column C of spreadsheet1 with column A of spreadsheet2. Both of these spreadsheet has records more than 8000. Due to huge number of records my script constantly gives exceeded maximum execution time error. Here is the script
function compare() {
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sheetList1=ss.getSheetByName("spreadsheet1");
var sheetList2=ss.getSheetByName("spreadsheet2");
var sheet1Data=sheetList1.getRange(2,3,sheetList1.getLastRow(),1).getValues();
var sheet2Data=sheetList2.getRange(1,1,sheetList2.getLastRow(),1).getValues();
for (i in sheet2Data){
var row = 2;
for (j in sheet1Data){
if (sheet1Data[j][0]==sheet2Data[i][0]){
sheetList1.getRange("A"+row).setValue('Inactive');
}
row++;
}
}
}
any suggestions for optimizing this script. Or how to handle this error ?
Thanks in advance :)
EDIT
Thanks for the wonderful reply. There is one issue. If I push data in newSheet1Data array before the if statement then it write Inactive twice. i.e if there are two rows it writes inactive to four rows.Like
newSheet1Data.push(sheet1Data[j]);
if (sheet1Data[j][2]==sheet2Data[i][0]){
newSheet1Data[j][0]='Inactive';
}
If I push data inside if statement and no match occur then it does not find row and give this error TypeError: Cannot set property "0.0" of undefined to "Inactive". Like
if (sheet1Data[j][0]==sheet2Data[i][0]){
newSheet1Data.push(sheet1Data[j]);
newSheet1Data[j][0]='Inactive';
}
You should avoid any calls to spreadsheet API in a loop, especially when you have so much data in it.
The idea is to play only with arrays and to write the result back once it is finished.
The code below does it (based on you code if data in col C sheet1 is the same as data in sheet2 col A then write a string in sheet1 col A ).
I hope I made no mistake but I didn't have the opportunity to test my code... it might need some debugging ;-)
function compare() {
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sheetList1=ss.getSheetByName("Sheet1");
var sheetList2=ss.getSheetByName("Sheet2");
var sheet1Data=sheetList1.getDataRange().getValues();// get the whole sheet in an array
var sheet2Data=sheetList2.getRange(1,1,sheetList2.getLastRow(),1).getValues();
var newSheet1Data = [] ; // create a new array to collect data
for (i=0;i<sheet1Data.length;++i){
for (j=0;j<sheet2Data.length;++j){
if(i!=j){continue};
newSheet1Data.push(sheet1Data[i]); // add the full row to target array
if (sheet1Data[i][2]==sheet2Data[j][0]){
newSheet1Data[i][0]='Inactive';//if condition is true change column A
break
}
}
}
newSheet1Data.shift();// remove first row (probably headers ?)
sheetList1.getRange(2,1,newSheet1Data.length,newSheet1Data[0].length).setValues(newSheet1Data); // write back to sheet1 in one batch
}
EDIT : after seing your doc I understand what you want more exactly...
here is the new code (tested on your SS)
function compare() {
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sheetList1=ss.getSheetByName("Sheet1");
var sheetList2=ss.getSheetByName("Sheet2");
var sheet1Data=sheetList1.getDataRange().getValues();// get the whole sheet in an array
var sheet2Data=sheetList2.getRange(1,1,sheetList2.getLastRow(),1).getValues();
var newSheet1Data = [] ; // create a new array to collect data
for (i=0;i<sheet1Data.length;++i){
newSheet1Data.push(sheet1Data[i]); // add the full row to target array
for (j=0;j<sheet2Data.length;++j){
if (sheet1Data[i][2]==sheet2Data[j][0]){
newSheet1Data[i][0]='Inactive';//if condition is true change column A
break;// don't continue after condition was true, this will speed up the process
}
}
}
newSheet1Data.shift();// remove first row (probably headers ?)
sheetList1.getRange(2,1,newSheet1Data.length,newSheet1Data[0].length).setValues(newSheet1Data); // write back to sheet1 in one batch
}