I'm new to Google Apps Script and I'm trying to make a script where I'll take a single string value and copy to multiple rows in a google sheet. I've taken an array to save the single value multiple times. But still I can't get it done. Every time I run the script, I get this error,
Cannot convert Array to Object[][]
Here are my codes,
function myFun() {
var ss = SpreadsheetApp.openById(SHEET_ID);
var sheet = ss.getSheetByName("Form Responses");
var new_vals = sheet.getRange(2, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues();
var master_ss = SpreadsheetApp.getActiveSpreadsheet();
var master_sheet = master_ss.getSheetByName("Sheet1");
var lr = master_sheet.getLastRow()+1;
var ss_real_name = "District";
var ss_real_names = [];
for (var i=0; i<new_vals.length; i++)
{
ss_real_names.push(ss_real_name);
}
master_sheet.getRange(lr, 1, new_vals.length).setValues(ss_real_names);
}
Is there something wrong in my code? How can I save the single string value in multiple rows?
Google Apps script writes values as arrays of arrays with every array inside of the outer array being a row and the elements in the inner arrays going into the columns.
If you want to write the data as rows you need to create an array filled with one element arrays. Try ss_real_names.push([ss_real_name]);.
If you wanted to write them as a column vector you could just say setValues([ss_real_names]) instead.
Related
I have a large google sheet with 30275 rows and 133 columns in a google sheet. I want to filter the data and copy column AZ to another spreadsheet.
Link to spreadsheet: https://docs.google.com/spreadsheets/d/1aiuHIYzlCM7zO_5oZ0aOCKDwPo06syXhWvhQMKgJE2I/edit?usp=sharing
I have been trying to follow this link
I am not that familiar with javascript and the code is designed to exclude items from filter rather than including items on filter. I have 500+ items to exclude so need to work out something that will be efficient in filtering large dataset in short time before execution limit is reached.
Here is my code so far. Any help to get this working would be appreciated.
NOTE: Filter/ Query with importrange formulas dont work due to the large volume of data. So I need an efficient script to filter large dataset and move them to another sheet before execution time limit.
function filtered() {
var ss = SpreadsheetApp.openById('1u9z_8J-tvTZaW4adO6kCk7bkWeB0pwPcZQdjBazpExI');
var sheet = ss.getSheetByName('Sheet1');
var destsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('JockeyList');
var demosheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Betting data - Demo');
var jockey = demosheet.getRange('L14').getValues();
// Get full (non-filtered) data
var values = sheet.getRange('A:EC').getValues();
// Apply filter criteria here
//Logger.log(jockey);
var hiddenValues = jockey;
values = values.filter(function(v) {
return hiddenValues.indexOf(v[51]) == 1;
});
Logger.log(values.length);
// Set filtered data on the target sheet
destsheet.getRange(10, 5, values.length, 2).setValues(values);
}
Ok so it seems like you want to copy only the values from AZ in 'Sheet1' that are equal to whatever string value is contained in cell L14 of sheet 'Betting data - Demo.' If that is the case, then here is a change to your original code that will accomplish that:
function filtered() {
var ss = SpreadsheetApp.openById('1u9z_8J-tvTZaW4adO6kCk7bkWeB0pwPcZQdjBazpExI');
var sheet = ss.getSheetByName('Sheet1');
// this assumes that your destsheet and demosheet are in the same spreadsheet. Keep in mind that opening spreadsheets with SpreadsheetApp is costly so you want to minimize your calls to new sheets.
var destsheet = ss.getSheetByName('JockeyList');
var demosheet = ss.getSheetByName('Betting data - Demo');
var jockey = demosheet.getRange('L14').getValue();
var searchTerm = new RegExp(jockey);
// Get full (non-filtered) data
var values = sheet.getRange('A:EC').getValues();
// Apply filter criteria here and return ONLY THE VALUE IN COLUMN AZ
var filteredValues = values.reduce(function(resultArray, row) {
if (searchTerm.test(row[51])) resultArray.push([row[51]]);
return resultArray;
}, [])
// Set filtered data on the target sheet
// Note* not clear why you are starting at row 10, but as is this will overwrite all of the data in column 5 of destsheet starting from row 10 every time this function runs
destsheet.getRange(10, 5, filteredValues.length, 1).setValues(filteredValues);
}
As it says in the code sample, this will only copy and paste the value in column AZ of 'sheet1'. It does not alter 'sheet1' in any way. If that is really all you want to do, then this function works, but it's overkill. Since you are just filtering values in AZ against a single string value, it would be more efficient to simply count the number of times that string occurs in column AZ and then add the string that number of times to your destination sheet.
Also note that your original function is pasting values into destsheet into a constant row (row 10). That means that every time your function runs, the data from row 10 on will be overwritten by whatever data you are copying from 'sheet1'
I am trying to get print the names of all the sheets in a Google Spreadsheet in one column and their respective IDs (part of the URL) in another column. Here is my script so far
function getSchedule() {
var ss=SpreadsheetApp.openById("Sheet_ID");
var sh=ss.getActiveSheet();
var allSheets=ss.getSheets();
var ids=[];
for(var i=0;i<allSheets.length;i++)
{
ids[allSheets[i].getName()]=allSheets[i].getSheetId();
}
var invoicess = SpreadsheetApp.getActiveSpreadsheet();
var schedule = invoicess.getSheetByName("Schedule");
schedule.getRange("A1:B").clear();
var headers = [["Sheet Name", "ID"]];
schedule.getRange("A1:B1").setValues(headers);
for(var d = 1; d=allSheets.length;d++){
schedule.getRange(d+1,1).setValue(allSheets[d]);}
}
So I believe that allSheets should be an array of all the sheet names and ids is an array of the IDs, but the last for loop that I believe should print the values of the allSheets array does not print anything. I would like to have it print starting in cell A2. I do not have a loop for the IDs yet. Can anyone tell me why nothing is printing in the cells?
Issues:
The following line is not going to add new values to the array:
ids[allSheets[i].getName()]=allSheets[i].getSheetId();
As a result, data are not added to the array and this is the reason you are not seeing data in your sheet. Instead you should use push() to add elements (rows) to the array.
Also since you are pasting a 2D array of values, you need to use setValues instead of setValue.
Your code uses unnecessary lines of code. For example you use setValues to add the header in your file when you can simply add it to the original array in which the data is going to be appended. You don't need for loops to append values with setValues.
I improved your code by removing redundant calls and made it more JavaScript friendly.
Improved Solution:
function getSchedule() {
const ss=SpreadsheetApp.openById("Sheet_ID");
const invoicess = SpreadsheetApp.getActiveSpreadsheet();
const schedule = invoicess.getSheetByName("Schedule");
const allSheets = ss.getSheets();
const ids = [["Sheet Name", "ID"]];
allSheets.forEach(sh=>{
ids.push([sh.getName(),sh.getSheetId()]);
});
schedule.getRange("A1:B").clear();
schedule.getRange(1,1,ids.length,ids[0].length).setValues(ids);
}
Instead of this ids[allSheets[i].getName()]=allSheets[i].getSheetId();
try this: ids.push([allSheets[i].getName(),allSheets[i].getSheetId()])
and use setValues(ids) at the end.
The aim of my script is to loop through one column of data (Col 2 in my example) and where the cell says 'Approved' then adjust the formula which is sitting in the corresponding Col1 to be saved as a value. The script below achieves this but runs awfully slowly - can anyone help in speeding it up?
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = ss.getSheetByName('data');
var tracker = ss.getSheetByName('Tracker');
var rowlength = tracker.getLastRow();
for (r=2; r<rowlength+1; r++) {
var ApprovedCell = tracker.getRange(r,2).getValue();
if (ApprovedCell == 'Approved'){
var FormulaCell = tracker.getRange(r,1);
FormulaCell.copyTo(FormulaCell,{contentsOnly:true});
}}
}
Explanation:
The issue with your current solution is that you are iteratively using getValue, getRange and copyTo which is an extremely inefficient approach especially when the size of the data becomes large.
Instead you can use getValues() and getFormulas() to get all the values and the formulas respectively, of the given range, and then use setValues() to set all the desired values/formulas back to the sheet.
The following script will iterate over the values with a forEach() loop and will store the value, if cell in column B is 'Approved', otherwise store the formula, to an empty array repAr.
Then you can efficiently use a single line of code to set all the values/formulas back to column A:
tracker.getRange(2,1,repAr2D.length,1).setValues(repAr2D);
Solution:
function myFunction(){
const ss = SpreadsheetApp.getActiveSpreadsheet();
const tracker = ss.getSheetByName('Tracker');
const vals = tracker.getRange('A2:B'+tracker.getLastRow()).getValues();
const formulas = tracker.getRange('A2:A'+tracker.getLastRow()).getFormulas().flat();
const repAr = [];
vals.forEach((r,i)=>
repAr.push(r[1]=='Approved'?r[0]:formulas[i]));
const repAr2D = repAr.map(r=>[r]);
tracker.getRange('A2:A'+tracker.getLastRow()).clearContent();
tracker.getRange(2,1,repAr2D.length,1).setValues(repAr2D);
}
Bonus info:
Instead of a regular if condition, I used a ternary operator to make the code more clear.
getFormulas() returns a 2D array and this is why I am using flat() to convert it to a 1D array. This is more convenient given that the range is a single column and you can just slice the array with a single index.
It is a good practice to clear the content before you set data to a range that already contains some data.
I have a google sheet like this
The number of columns and rows may be different (not static). I want my google script to look for last row and column, then run a for loop and combine data of each row into this json format like this.
[["1","Lee","Blue","Active"],["2","Mike","Green","Disabled"],["3","Chan","Yellow","Active"]]
I learned about the JSON.stringify() function that helps and I am able to combine data or each row into json format BUT i want each row data separated by a comma (,), not sure how to add that in the logic since I DO NOT want comma to be added after LAST row.
I have tried creating this code and need help with the logic to dynamically run the loop based on number of columns and rows instead of hard-coding any range and add comma.
var mysheet = SpreadsheetApp.getActive().getSheetByName('mysheet');
function combine_val() {
GetRowsCount();
var startRow = 2; // First row of data to process
var numRows = LastAudienceRow; // Number of rows to process
for (var i = 0; i < numRows; ++i) {
var dataRange = mysheet.getRange(startRow, 1, 1, 4);
var data = dataRange.getValues();
startRow = startRow+1;
var data2= JSON.stringify(data);
}
SpreadsheetApp.getUi().alert(data2);
}
function GetRowsCount() {
LastAudienceRow = mysheet.getLastRow() - 1;
return LastAudienceRow;
}
You've rather over complicated the whole situation, as much of this is done automatically. This script is also not very efficient and when you add more runs, will take too long to execute, eventually hitting the App script limit for maximum execution time.
Here you are trying to get the data from each row individually, and add it to an array. This is very inefficient, and it's much better/faster to just specify the range of data you want. Happily, this also formats it's pretty much as you like.
Here's a sample script:
var mysheet = SpreadsheetApp.getActive().getSheetByName('mysheet');
function combine_val() {
var startRow = 1; // First row of data to process.
var startColumn = 1; //First Column to process, in case that changes.
var numRows = mysheet.getLastRow(); // Number of rows to process
var numCols = mysheet.getLastColumn(); //Also the number of columns to process, again in case that changes.
var dataRange = mysheet.getRange(startRow, startColumn, numRows, numCols);//Get the full range of data in the sheet dynamically.
var data = JSON.stringify(dataRange.getValues());//Get the value of the range, AND convert it to a JSON string in one line.
Logger.log(data); //Use the in built logger to read the values that are returned. You can read this by pressing 'ctrl+enter'.
}
I've also added in some sample dynamic improvements which will mean you are hard coding less of your values into your scripts, preventing it from breaking if the data changes.
Additionally, I have added in a sample of how to read the data via the inbuilt logging tool, rather then via a cumbersome pop up after every run.
JSON.stringify already returns row data separated by commas.
var arr = [["1","Lee","Blue","Active"],["2","Mike","Green","Disabled"],["3","Chan","Yellow","Active"]];
console.info(JSON.stringify(arr))
The above example prints the following string to the console
[["1","Lee","Blue","Active"],["2","Mike","Green","Disabled"],["3","Chan","Yellow","Active"]]
The following example prints each row values separated by comma.
var arr = [["1","Lee","Blue","Active"],["2","Mike","Green","Disabled"],["3","Chan","Yellow","Active"]];
for(var i = 0; i < arr.length; i++){
console.info(JSON.stringify(arr[i]).replace(/[\[\]]/g,''));
}
To use it on as a Google Sheets / Apps Script instead of assigning a literal to arr use getValues() . You could replace console.info by SpreadsheetApp.getUi.alert(message) too.
I am writing a script for a Google Docs Spreadsheet to read a list of directors and add them to an array if they do not already appear within it.
However, I cannot seem to get indexOf to return anything other than -1 for elements that are contained within the array.
Can anyone tell me what I am doing wrong? Or point me to an easier way of doing this?
This is my script:
function readRows() {
var column = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("Director");
var values = column.getValues();
var numRows = column.getNumRows();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var directors = new Array();
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (directors.indexOf(row) == -1) {
directors.push(row);
} else {
directors.splice(directors.indexOf(row), 1, row);
}
}
for (var i = 2; i < directors.length; i++) {
var cell = sheet.getRange("F" + [i]);
cell.setValue(directors[i]);
}
};
When you retrieve values in Google Apps Script with getValues(), you will always be dealing with a 2D Javascript array (indexed by row then column), even if the range in question is one column wide. So in your particular case, and extending +RobG's example, your values array will actually look something like this:
[['fred'], ['sam'], ['sam'], ['fred']]
So you would need to change
var row = values[i];
to
var row = values[i][0];
As an aside, it might be worth noting that you can use a spreadsheet function native to Sheets to achieve this (typed directly into a spreadsheet cell):
=UNIQUE(Director)
This will update dynamically as the contents of the range named Director changes. That being said, there may well be a good reason that you wanted to use Google Apps Script for this.
It sounds like an issue with GAS and not the JS. I have always had trouble with getValues(). Even though the documentation says that it is a two dimensional array, you can't compare with it like you would expect to. Although if you use an indexing statement like values[0][1] you will get a basic data type. The solution (I hope there is a better way) is to force that object into a String() and then split() it back into an array that you can use.
Here is the code that I would use:
var column = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("Director");
var values = column.getValues();
values = String(values).split(",");
var myIndex = values.indexOf(myDirector);
If myDirector is in values you will get a number != -1. However, commas in your data will cause problems. And this will only work with 1D arrays.
In your case: var row = values[i]; row is an object and not the string that you want to compare. Convert all of your values to an array like I have above and your comparison operators should work. (try printing row to the console to see what it says: Logger.log(row))
I ran into a similar problem with a spreadsheet function that took a range as an object. In my case, I was wanting to do a simple search for a fixed set of values (in another array).
The problem is, your "column" variable doesn't contain a column -- it contains a 2D array. Therefore, each value is it's own row (itself an array).
I know I could accomplish the following example using the existing function in the spreadsheet, but this is a decent demo of dealing with the 2D array to search for a value:
function flatten(range) {
var results = [];
var row, column;
for(row = 0; row < range.length; row++) {
for(column = 0; column < range[row].length; column++) {
results.push(range[row][column]);
}
}
return results;
}
function getIndex(range, value) {
return flatten(range).indexOf(value);
}
So, since I wanted to simply search the entire range for the existance of a value, I just flattened it into a single array. If you really are dealing with 2D ranges, then this type of flattening and grabbing the index may not be very useful. In my case, I was looking through a column to find the intersection of two sets.
Because we are working with a 2D array, 2dArray.indexOf("Search Term") must have a whole 1D array as the search term. If we want to search for a single cell value within that array, we must specify which row we want to look in.
This means we use 2dArray[0].indexOf("Search Term") if our search term is not an array. Doing this specifies that we want to look in the first "row" in the array.
If we were looking at a 3x3 cell range and we wanted to search the third row we would use 2dArray[2].indexOf("Search Term")
The script below gets the current row in the spreadsheet and turns it into an array. It then uses the indexOf() method to search that row for "Search Term"
//This function puts the specified row into an array.
//var getRowAsArray = function(theRow)
function getRowAsArray()
{
var ss = SpreadsheetApp.getActiveSpreadsheet(); // Get the current spreadsheet
var theSheet = ss.getActiveSheet(); // Get the current working sheet
var theRow = getCurrentRow(); // Get the row to be exported
var theLastColumn = theSheet.getLastColumn(); //Find the last column in the sheet.
var dataRange = theSheet.getRange(theRow, 1, 1, theLastColumn); //Select the range
var data = dataRange.getValues(); //Put the whole range into an array
Logger.log(data); //Put the data into the log for checking
Logger.log(data[0].indexOf("Search Term")); //2D array so it's necessary to specify which 1D array you want to search in.
//We are only working with one row so we specify the first array value,
//which contains all the data from our row
}
If someone comes across this post you may want to consider using the library below. It looks like it will work for me. I was getting '-1' return even when trying the examples provide (thanks for the suggestions!).
After adding the Array Lib (version 13), and using the find() function, I got the correct row!
This is the project key I used: MOHgh9lncF2UxY-NXF58v3eVJ5jnXUK_T
And the references:
https://sites.google.com/site/scriptsexamples/custom-methods/2d-arrays-library#TOC-Using
https://script.google.com/macros/library/d/MOHgh9lncF2UxY-NXF58v3eVJ5jnXUK_T/13
Hopefully this will help someone else also.
I had a similar issue. getValues() seems to be the issue. All other methods were giving me an indexOf = -1
I used the split method, and performed the indexOf on the new array created. It works!
var col_index = 1;
var indents_column = main_db.getRange(1,col_index,main_db.getLastRow(),1).getValues();
var values = String(indents_column).split(","); // flattening the getValues() result
var indent_row_in_main_db = values.indexOf(indent_to_edit) + 1; // this worked
I ran into the same thing when I was using
let foo = Sheet.getRange(firstRow, dataCol, maxRow).getValues();
as I was expecting foo to be a one dimensional array. On research for the cause of the apparently weird behavior of GAS I found this question and the explanation for the always two dimensional result. But I came up with a more simple solution to that, which works fine for me:
let foo = Sheet.getRange(firstRow, dataCol, maxRow).getValues().flat();