How do I increment a Google Sheets range via getRange? - google-apps-script

function namedRanges() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
for(var i=3; i<504; i++) {
var r = ss.getRange('A3:Q3'+i);
ss.setNamedRange('Song'+i, r);
}
}
The above formula, is not stepping thru the 500 song rows in my spreadsheet and naming each row, "Song 3" for row A3-Q3, etc. (starting at row 3 as I have headers in rows 1 and 2) as I expected.
So what I am trying to do is create named ranges for all 500 songs in the sheet.
What I am expecting:
Name - Range
Song3 - Songs!A3:Q3
Song4 - Songs!A4:Q4
Song5 - Songs!A5:Q5
etc.
What I am getting:
Song3 - Songs!A3:Q33
Song4 - Songs!A3:Q34
Song5 - Songs!A3:Q35
etc.
I have spent two days trying to track this down by searching (in vain). I'm sure it's easy. Anybody know how to do this?
Improved version
Below is a "new and better" version of the script I am using (which also incorporates #Casper 's answer). It is "new" and "better" because it counts the number of columns in the sheet, rather than taking a static value which makes it more flexible as it will name a range the width of the sheet, no matter that width (as measured by columns).
function namedRanges() {
var ss = SpreadsheetApp.openByUrl("url");
var sheet = ss.getActiveSheet();
//first column = 1
var numcols = sheet.getLastColumn()
Logger.log(numcols);
for(var i=101; i<501; i++) {
var r = sheet.getRange(i,1,1,numcols);//row, column, num rows, num cols
ss.setNamedRange('Song'+i, r);
}
}

You're looking for something like this I suppose:
function namedRanges() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
for(var i=3; i<504; i++) {
var r = ss.getRange('A'+i+':Q'+i);
ss.setNamedRange('Song'+i, r);
}
}
Your original code was appending the row number to your static text:
var r = ss.getRange('A3:Q3'+i);
Basically reads as:
var r = ss.getRange('A3:Q3'+3);
The + sign concatenates Q3 and 3 resulting in Q33. However, in my code you define the columns as static text and the row numbers as variable.
var r = ss.getRange('A'+i+':Q'+i);
Therefore reads as (i=3):
var r = ss.getRange('A'+3+':Q'+3);
Resulting in A3:Q3
Hope that helps.

Related

Exception: 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 1

I am very new to javascript and have searched around a ton for this and can't seem to find the issue with my code. I am attempting to simply write a code that will copy the values in a column from a pivot table sheet in Google Sheet and then paste the values in another sheet. However, before pasting the values, I want each individual value to be duplicated 12 times (for 12 months). So, assuming I have 10 unique values (A, B, C, D, E, F, G, H, I, J) that I am copying, I want to return value A 12 times in a row, then value B 12 times in a row, etc.
I run getValues, which seems to put the values in a 2 dimensional array. I've then taken this temp_array that I had created and used a for loop to duplicate each value 12 times in a new array.
However, when I setValues, I am pasting the values in my spreadsheet correctly, but I get this error message regardless (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 1.), any ideas why?
Here is a small example of what my input could look like (1st image) and what I would want the output to look like (2nd image)
function test2() {
// activating current spreadsheet for use
var spreadsheet = SpreadsheetApp.getActive();
//empty array
var array_dept_temp = [];
// returns cell position (ex: C5) of the last row of the pivot table 1 sheet that has content in column 1
var last_row = spreadsheet.getSheetByName("Pivot Table 1").getRange("A:A").getNextDataCell(SpreadsheetApp.Direction.DOWN).getRowIndex();
//subtracting 1 from last row because we are excluding the headers. This gives us our row_length
var row_length = last_row - 1
var array_dept = [[]]
array_dept = new Array(row_length*12)
//new Array(row_length*12);
// Get value in pivot table 1 from range of row 2 (dept name, but exclude the header), column 1, all the way to last row
// Then paste it in sheet5 from row 1, column 3, all the way to the last row defined above
array_dept_temp = spreadsheet.getSheetByName("Pivot Table 1").getRange(2,1, last_row).getValues();
for (var i = 1; i < row_length; i++ )
{
//get value and then paste it in a destination
array_dept.fill(array_dept_temp[i-1], (-12 + (12*i)) , 12*i);
}
var destination_dept = spreadsheet.getSheetByName("Sheet5").getRange(2,3,row_length*12);
destination_dept.setValues(array_dept);
}
Suggestion / Alternate solution:
Try:
function test() {
  var spreadsheet = SpreadsheetApp.getActive();
  var sheet = spreadsheet.getSheetByName("Pivot Table 1");
  var array_dept_temp = sheet.getRange(2,1, sheet.getLastRow()-1).getValues();
  var array_dept = [];
  for (var i = 0; i < array_dept_temp.length; i++) {
    array_dept = [...array_dept, ...Array.apply(null, Array(12)).map(function(){return array_dept_temp[i]})]
  }
  var destination_dept = spreadsheet.getSheetByName("Sheet5").getRange(2,3,array_dept.length);
  destination_dept.setValues(array_dept);
}
Result:
Another way without using fill or from.
Also some modification, you can just use .getLastRow() function to get the last row, however take not that if there is data below it will count all the rows including the blank until the row that has data. And you may also use .length on your data to setValue.
From your showing sample input and output situations, how about the following modified script?
Modified script:
function test2_sample() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var srcSheet = ss.getSheetByName("Pivot Table 1");
var dstSheet = ss.getSheetByName("Sheet5");
var srcValues = srcSheet.getRange("A2:A" + srcSheet.getLastRow()).getValues();
var dstValues = srcValues.flatMap(a => Array(12).fill(a));
dstSheet.getRange(2, 3, dstValues.length).setValues(dstValues);
}
When this script is run using your sample input sheet, I think that your expected output values are obtained.
Now, I thought that var dstValues = srcValues.flatMap(([a]) => Array(12).fill(a).map(e => [e])); can be modified to var dstValues = srcValues.flatMap(a => Array(12).fill(a));. This is simpler.
From your reply of Are you able to explain what this does? var dstValues = srcValues.flatMap(([a]) => Array(12).fill(a).map(e => [e]));, in this script, var dstValues = srcValues.flatMap(([a]) => Array(12).fill(a).map(e => [e])); can be also modified as follows. I thought that this might also help to understand it.
function test2_sample() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var srcSheet = ss.getSheetByName("Pivot Table 1");
var dstSheet = ss.getSheetByName("Sheet5");
var srcValues = srcSheet.getRange("A2:A" + srcSheet.getLastRow()).getValues();
var dstValues = [];
for (var i = 0; i < srcValues.length; i++) {
dstValues = dstValues.concat(Array(12).fill(srcValues[i]));
}
dstSheet.getRange(2, 3, dstValues.length).setValues(dstValues);
}
Note:
As additional information, when your showing script is modified, how about the following modification? In your script, I thought that it is required to add the values to array_dept in the loop. And, it is required to flatten the elements in the array.
function test2() {
var spreadsheet = SpreadsheetApp.getActive();
var array_dept_temp = [];
var last_row = spreadsheet.getSheetByName("Pivot Table 1").getRange("A:A").getNextDataCell(SpreadsheetApp.Direction.DOWN).getRowIndex();
var row_length = last_row - 1
var array_dept = []
array_dept_temp = spreadsheet.getSheetByName("Pivot Table 1").getRange(2, 1, last_row).getValues();
for (var i = 0; i < row_length; i++) {
array_dept = [...array_dept, ...Array(12).fill(array_dept_temp[i])];
}
var destination_dept = spreadsheet.getSheetByName("Sheet5").getRange(2, 3, array_dept.length);
destination_dept.setValues(array_dept);
}
Reference:
flatMap()

Vlookup + indexOf to find values in a CSV via Google App Script without using loop

The main idea is not to need looping to generate a VLOOKUP because it generates a huge slowdown when the amount of data is very large.
To VLOOKUP on data directly in the sheet I do as follows:
function myFunction() {
var s = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var searchValue = s.getRange("Test!A1").getValue();
var data = SpreadsheetApp.openById("XXXXXXXXXXXX").getSheetByName("Test 2");
var dataValues = data.getRange("A1:A").getValues();
var dataList = dataValues.join("ღ").split("ღ");
var index = dataList.indexOf(searchValue);
if (index === -1) {
s.getRange("Test!B1").setValue('off');
} else {
var row = index + 1;
var foundValue = data.getRange("D"+row).getValue();
s.getRange("Test!B1").setValue(foundValue);
}
}
But there is a big problem in this method, because when many different accounts try to access this sheet at the same time, the error type error: could not connect sheet xxxxx appears or causes huge delay sometimes.
So what was the solution I found? Publish spreadsheet pages as CSV so they can be used and this error doesn't happen when many accounts call the same spreadsheet.
Currently, as I haven't found a way to use indexOf using the first column when I import the CSV with several columns of data, I had to create a spreadsheet page only with the copy data of column A, and then I got to the final result of VLOOKUP like this:
(the value in var searchValue in this example case will be two)
function myFunction() {
var s = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var url_columnA = 'AAAAA';
var url_vlookup = 'BBBBB';
var dataSearch = Utilities.parseCsv(UrlFetchApp.fetch(url_columnA));
var dataList = dataSearch.join("ღ").split("ღ");
var searchValue = s.getRange("Test!A1").getValue();
var index = dataList.indexOf(searchValue);
if (index === -1) {
s.getRange("Test!B1").setValue('off');
} else {
var row = index;
var dataVlookup = Utilities.parseCsv(UrlFetchApp.fetch(url_vlookup));
var foundValue = dataVlookup[row][3];
s.getRange("Test!B1").setValue(foundValue);
}
}
Return example:
other number
var url_vlookup:
Col A
Col B
Col C
Col D
home
1
a
win
away
2
b
loose
one
3
c
number
two
4
d
other number
three
5
e
number again?
var url_columnA:
Col A
home
away
one
two
three
Is there any way to handle var url_vlookup data for search the value in column A so that it's not necessary to use this page var url_columnA separated or is the only way to do it without looping?
The first column can easily be separated after parsing using Array.map:
const dataVlookup = Utilities.parseCsv(UrlFetchApp.fetch(url_vlookup));
const url_columnA = dataVlookup.map(row => row[0])

How to choose a destination spreadsheet of copied data based on spreadsheet name

So I have two tables. Table 1 is a large table with 7 columns. In this table, C2:C has unique ID numbers of people. Table 2 is a smaller table with the names of all people in K3:K and the unique ID on J3:J. Table 2 only has 2 columns, the IDs and the Name. So, All IDs in C2:C exists in K3:K. All the names in K3:K are also the names of sheets in the same worksheet. So what I'm trying to do is:
Loop through the IDs in table 1 (large table) with the IDs in table 2 (small table). If the IDs are the same, then I will copy the whole row in table 1 into my destination sheet. To choose my destination sheet, I check the cell adjacent to the identified ID in table 2 and choose the sheet whose name is the same.
I hope i explained that decently. My problem it's copying things in every 10 intervals. So it copies the 10th row, then the 20th, then the 30th... and I'm confused how to even approach figuring out where I went wrong because I don't understand why it's in 10 interverals and why it's choosing the sheets that its choosing.
If it helps, link to the sheet is: https://docs.google.com/spreadsheets/d/1522hM3mO9AdaTpiS2oI1IK3wwwFXbOz1qHxcMrRzASY/edit?usp=sharing
function copystuff() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var key = SpreadsheetApp.getActiveSheet().getName();
var currentS = ss.getSheetByName(key);
var sheetNameArray = [];
sheetNameArray = sheets.map(function(sheet){ // puts sheet names into an array
return [sheet.getName()];
});
var name_master = currentS.getRange("K3:K").getValues().flat().filter(r=>r!=''); //key name
var enno_master = currentS.getRange("J3:J").getValues().flat().filter(r=>r!=''); //key ID
var enno_all = currentS.getRange("C2:C").getValues().flat().filter(r=>r!=''); // number of big table
for (x = 0; x< enno_master.length; x++){ //to loop through the key number
for (y = 0; y < enno_all.length; y++){ // to loop through the numbers of big table
if(enno_master[x]==enno_all[y]){ // if ID in column C = name in column J
for(z = 0; z < sheetNameArray.length; z++){ //looping through my sheets
if(name_master[x] == sheetNameArray[[z]]){ //if name in column K, which is of the same row as the key number
var copyrange = currentS.getRange("A"+y+2+":G"+y+2).getValues(); //y is the row in table 1 where the IDs are the same.
var destfile = ss.getSheetByName(sheetNameArray[[z]]); //copying to sheet Z.
destfile.getRange(destfile.getLastRow()+1,1,1,7).setValues(copyrange); //paste
}
}
}}}}
The thing that makes it easy is turning your small table into an object so that when the object is given the number in column 3 of the big table it returns the actual sheet object. The array pA in object sht provides you with the ability to check using indexOf() if the Sheet actually exists in the object without having to use hasOwnProperty().
function copyStuff() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet1');
const objvs = sh.getRange(3,10,getColumnHeight(10,sh,ss)-2,2).getValues();
const vs = sh.getRange(2,1,sh.getLastRow() - 1,7).getValues();
let sht = {pA:[]};
objvs.forEach(r => {sht[r[0]]=ss.getSheetByName(r[1]); sht.pA.push(r[0])});//This creates an object which correlates enNo to a sheet making it possible to greatly simplify your code.
vs.forEach(r => {
if(~sht.pA.indexOf(r[2])) {//this determines if the sheet is defined in sht object if it is then you can do a copy because the sheet exists
let s = sht[r[2]];
if(s) {
s.appendRow(r);//this appends the entire row to the next empty row at the bottom of data. You could also use copyTo or setValues(). This is a simple approach.
}
}
});
}
Helper function: this function is used to the the height of column j. It's sort of the getLastRow() but only for a column:
function getColumnHeight(col, sh, ss) {
var ss = ss || SpreadsheetApp.getActive();
var sh = sh || ss.getActiveSheet();
var col = col || sh.getActiveCell().getColumn();
var rcA = [];
if (sh.getLastRow()){ rcA = sh.getRange(1, col, sh.getLastRow(), 1).getValues().flat().reverse(); }
let s = 0;
for (let i = 0; i < rcA.length; i++) {
if (rcA[i].toString().length == 0) {
s++;
} else {
break;
}
}
return rcA.length - s;
}
~ Bitwise Not
The easiest way to find information in the documentation is to use the index in it. Especially since they just changed it all and many of us no longer know where everything is.
SUGGESTION:
Since you want to check the large table A:G & then copy each table data that matches every user ID on the second table J:K and move those matched rows to the designated sheet named after the IDs' user names, you can refer to this sample implementation below:
function copystuff() {
var ss = SpreadsheetApp.getActiveSheet();
var main =SpreadsheetApp.getActiveSpreadsheet();
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var sheetNameArray = sheets.map(function(sheet){
return [sheet.getName()]});
var lastRowK = ss.getRange(ss.getLastRow(),11).getNextDataCell(SpreadsheetApp.Direction.UP).getRow(); //Get the last row of column K on any worksheet
var user = main.getSheetByName(sheetNameArray[0]).getRange("J3:K"+lastRowK).getValues();
var tableData = main.getSheetByName(sheetNameArray[0]).getRange("A2:G"+main.getLastRow()).getDisplayValues();
var container = [];
for(x=0; x<user.length; x++){ //Loop every user from the second table
for(y=0; y<tableData.length; y++){ //loop through the large table data
if(tableData[y][2] == user[x][0]){ //check each user's ID if it has any matches on the user ID from the large table data
container.push(tableData[y]); //any matched row data from the large table will be placed temporarily on this container
}
}
if(container.length != 0){
Logger.log("User "+user[x][1]+ " with ID *"+ user[x][0] +"* has these data:\n"+container);
main.getSheetByName(user[x][1]).getRange(1,1,container.length,7).setValues(container);
}
container = []; //clean container for the next user
}
}
This script will get all table data and place them into an array variables user & tableData where it will be checked and processed to make the script run faster instead of live processing the sheet rows.
Sample Result:
After running the script, here are some of the results:
User name RJ with ID of 42:
User name May with ID of 7:
User name Angelo with ID of 25:
Here's the Log Results for review:

Google Apps Script: Get specific data and preview elsewhere

I have a sheet of a long (sorted) list, with column A with date and column B with time and then some data on columns c,d,e,f.
I want to have a script function that compares current date-time to the date and time from columns A & B and then copies to columns i,j,k,l,m,n on rows 1-4 the "upcoming" data.
So I'm thinking of something like this, but unsure how to finish it. Any ideas?
function myFunction()
{
var d = new Date();
var now = d.getTime();
var as = SpreadsheetApp.getActiveSheet();
var data = as.getDataRange().getValues(); //this seems to be wrong, because I don't need all DataRange, only first 6 columns, but as many rows as those having data
var c = 0;
for(i in data)
{
var row = data[i];
//var rowdate = ??;
if (now < rowdate)
{
c++;
//copy from row to destination location
}
if (c > 4) break;
}
}

Find string and get its column

Let's say I have a lot of columns and one of them contains "impressions" string (on row 3). What I need to do is to:
1) Find the cell with "impressions" string
2) Get column number or i.e. "D"
3) Based on what I got paste a formula into i.e. D2 cell which gets AVERAGE from a range D4:D*last*
I couldn't find it anywhere so I have to ask here without any "sample" code, since I have no idea on how to achieve what I want. (3rd one is easy but I need to get that "D" first)
There's no way to search in Google Apps Script. Below is a function that will accomplish the first 2 parts for you (by iterating over every cell in row 3 and looking for "impressions"):
function findColumnNumber() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1'); // insert name of sheet here
var range = sheet.getDataRange(); // get the range representing the whole sheet
var width = range.getWidth();
// search every cell in row 3 from A3 to the last column
for (var i = 1; i <= width; i++) {
var data = range.getCell(3,i)
if (data == "impressions") {
return(i); // return the column number if we find it
}
}
return(-1); // return -1 if it doesn't exist
}
Hopefully this will allow you to accomplish what you need to do!
The indexOf method allows one to search for strings:
function findColumnNumber() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet() //whatever tab the code is run on
var data = sheet.getDataRange().getValues();
var header_row_num = 1; // TODO: change this to whichever row has the headers.
var header = data[header_row_num -1] //Remember JavaScript, like most programming languages starts counting (is indexed) at 0. For the value of header_row_num to work with a zero-index counting language like JavaScript, you need to subtract 1
//define the string you want to search for
var searchString = "impressions";
//find that string in the header and add 1 (since indexes start at zero)
var colNum = header.indexOf(searchString) + 1;
return(colNum);