How find last line in a Google Sheet with getRange - javascript

I'm using the following script to find and replace text. The only problem is in the line:
var range = sheet.getRange("D2:D2000");
It creates new rows past my last row as it is set to 2000. I need to find the last row and then change this line of code. I'm lost :(
My total script is as follows:
function runReplaceInSheet() {
var spreadsheet = SpreadsheetApp.openById("1NK-pmvrJoSqKSgx2zRCAcRKGjk8SiiSsfwwerhfdM1SE"); // UPDATE ID
var sheet = SpreadsheetApp.setActiveSheet(spreadsheet.getSheets()[0]); // UPDATE number in square brackets
var range = sheet.getRange("D2:D2000");
// get the current data range values as an array
// Lesser calls to access the sheet, lower overhead
var startRow = 2; // First row of data to process
var numRows = 2; // UPDATE number of rows to process
// Fetch the range of cells
var dataRange = sheet.getRange(startRow, 4, numRows, 1) // Numbers of rows to process
// Fetch values for each row in the Range
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var v = row[3]; // edit: don't need this
var values = range.getValues();
// Replace Names
replaceInSheet(values, '1', 'Active');
replaceInSheet(values, '0', 'Inctive');
//write the updated values to the sheet, again less call;less overhead
range.setValues(values);
}
}
function replaceInSheet(values, to_replace, replace_with) {
//loop over the rows in the array
for (var row in values) {
//use Array.map to execute a replace call on each of the cells in the row.
var replaced_values = values[row].map(function(original_value) {
return original_value.toString().replace(to_replace, replace_with);
});
//replace the original row values with the replaced values
values[row] = replaced_values;
}
}

You can use sheet.getDataRange() to return a range that describes the area of the sheet with some data in it.
If you just want column D2:d you could do this
var range = sheet.getDataRange();
range = range.offset (1,3,range.getNumRows()-1,1);
Or you could get the data into a 1 dimensional array like this
var values = sheet.getDataRange().getValues().slice(1)
.map (function (d) {
return d[3];
});
There are many ways.

Related

How To Add Sheets Based on Cell Values?

I want to create a new worksheet each time I have a new user details in column 1 of my USERS sheet. Here is the code I have so far:
// Get the data from the sheet called CreateSheets
var sheetNames = SpreadsheetApp.getActive().getSheetByName("USERS").getDataRange().getValues();
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("USERS");
var data = ss.getDataRange().getValues();
var lr = ss.getLastRow();
var dataRange = ss.getRange(1, 1, lr, 1);
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
// For each row in the sheet, insert a new sheet and rename it.
sheetNames.forEach(function(row) {
var sheetName = data;
var sheet = SpreadsheetApp.getActive().insertSheet();
sheet.setName(sheetName);
});
}}
The code works but it is combining the data in the cells in column 1 into the name of the new spreadsheet. Thanks
function myfunc() {
const ss = SpreadsheetApp.getActive();
const ush = ss.getSheetByName("USERS");
const names = ush.getRange(1, 1, ush.getLastRow(), 1).getValues().flat();
const enames = ss.getSheets().map(s => s.getName());
names.forEach(n => {
if (~e.names.indexOf(n)) {
ss.insertSheet().setName(n);
enames.push(n); //add name to array to avoid duplicate names in column
}
});
}
Updated to account for existing sheet names and potential duplicate names in column 1.
I think this should do what you want. You should probably build in some sort of check to ensure the sheet doesn't already exist.
function makeSheetHappen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var wsUser = ss.getSheetByName("USERS");//the sheet with users
//this gets all values in column 1 to end of spreadsheet (it might include blanks)
//flat function avoids having to pull 2-dim array (h/t COOPER!)
var sheetNames = wsUser.getRange(1, 1, wsUser.getLastRow(), 1).getValues().flat();
//mapping function to get an array of the spreadsheet's sheet names.
var theExistingNames = ss.getSheets().map(function (aSheet) {
return aSheet.getName();
});
//loops through the sheetNames and ensures not blank and not currently existing.
sheetNames.forEach(function (aName) {
if (aName != '' && !theExistingNames.includes(aName)) {
var newSheet = ss.insertSheet();
newSheet.setName(aName);
theExistingNames.push(aName); //add name to array to avoid duplicate names in column
}
});
}

Copy rows after loop

after my last question I'm facing a problem with copying rows.
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName('ws1');
var startRow = 4;
var lastRow = sheet.getLastRow();
var numRows = lastRow - startRow + 1;
var lastCol = sheet.getLastColumn();
var dataSetValues = sheet.getRange(startRow, 1, numRows, lastCol).getValues();
for (var i = 0; i < numRows; i++){
let fVal = dataSetValues[i][5];
let gVal = dataSetValues[i][6];
let sum = +fVal + +gVal;
if (sum > 115) {
let row = dataSetValues[i];
}
}
What do I expect?
I wish set which columns to copy
I edited the code like this
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName('ws1');
var startRow = 4;
var lastRow = sheet.getLastRow();
var numRows = lastRow - startRow + 1;
var lastCol = sheet.getLastColumn();
var dataSetValues = sheet.getRange(startRow, 1, numRows, lastCol).getValues();
for (var i = 0; i < numRows; i++){
let aVal = dataSetValues[i][0];
let bVal = dataSetValues[i][1]; // + other columns
let fVal = dataSetValues[i][5];
let gVal = dataSetValues[i][6];
let sum = +fVal + +gVal;
if (sum > 115) {
let row = dataSetValues[i];
var ssDest = spreadsheet.getSheetByName('ws2');
var rngDest = ssDest.getRange(ssDest.getLastRow()+1,1);
//start copy
rngDest.setValues(row)
}
}
I get this error
The parameters (number[]) don't match the method signature for SpreadsheetApp.Range.setValues
Thanks
Your script just needs a few changes made to it:
1. It is important to note that the setValues() method accepts as parameter a two dimensional array in the form of Object[][].
You are simply passing it a one-dimensional array, hence the The parameters (number[]) don't match the method signature for SpreadsheetApp.Range.setValues error you are receiving.
In order to fix this, you will have to transform row into a 2 dimensional array and making the following changes
From
rngDest.setValues(row)
To
rngDest.setValues([row])
2. You will have to specify exactly the number of rows and the number of columns expected in the destination range.
After making the change above, you will end up running into a The number of columns in the data does not match the number of columns in the range error which is again expected. This is due to the fact that the getRange method will also need the number of rows and the number of columns such that when using setValues it will know exactly the structure of the data to set.
If you take a look at the getRange method:
getRange(row, column, numRows, numColumns)
Returns the range with the top left cell at the given coordinates with the given number of rows and columns.
In order to fix this, a simple change has to be made in order to indicate exactly the number of rows and the number of columns:
From
var rngDest = ssDest.getRange(ssDest.getLastRow()+1,1)
To
var rngDest = ssDest.getRange(ssDest.getLastRow() + 1, 1, 1, row.length);
As you can see, the number of rows here is 1 (as you are copying the data one row at a time) and the number of columns is equal to row.length (as the row variable has all the values corresponding to one row at a time).
Reference
Apps Script Range Class - setValues();
Apps Script Sheet Class - getRange();
Apps Script Troubleshooting.

Google Sheets Custom Script iterate through a range and print values in a determined order

UPDATE WITH a-change 's response and code
I am working on a function that will let me select a range in a sheet in Google Sheets and then paste the values that I am interested in into a specific order on another sheet.
Suppose RawData (Sheet1) looks like this:
I want to grab the range RawData!A1:L15, so basically everything that is that picture.
Afterwards I want to print it in another sheet (Sheet2 called Analysis) like so:
So far this is the code:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("RawData");
var targetSheet = ss.getSheetByName("Analysis");
var range = sheet.getDataRange();
var values = range.getValues();
for (var i in values) {
var row = values[i];
var column = 1;
for (var j in row) {
var value = row[j];
if (value !== "X") {
targetSheet.getRange(parseInt(i) + 1, column).setValue(value);
column++;
}
}
}
}
This code results in values being pasted in the 'Analysis' with the same order as in the 'RawData' sheet. The idea is for the data to be able to be pasted in a trio format, with no spaces between values. So the first trio would be: A1 = 1, B1 = 2, C1 = 3, A2 = 4, B2 = 5, C2 = 6, and so on.
A couple of things:
for (var row in values) { — here row is an index of an element, not the element itself. So it'll always be not equal to "X". Better to put it this way:
for (var i in values) {
var row = values[i];
}
Then you need to iterate over row to get to a single element and compare it with "X":
for (var i in values) {
var row = values[i];
for (var j in row) {
var value = row[j];
if (value !== "X") {
}
}
}
Next thing is pasting the value to your target sheet. The reason you are getting the same number in all the cells is that you're calling setValue on the whole A1:C8 cells range instead of one particular cell.
for (var i in values) {
var row = values[i];
var column = 1;
for (var j in row) {
var value = row[j];
if (value !== "X") {
targetSheet.getRange(parseInt(i + 1), column).setValue(value);
column++;
}
}
}
targetSheet.getRange(i, j) here gives you a single-cell precision.
So alltogether your code would look something like:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("RawData");
var targetSheet = ss.getSheetByName("Analysis");
var range = sheet.getDataRange();
var values = range.getValues();
for (var i in values) {
var row = values[i];
var column = 1;
for (var j in row) {
var value = row[j];
if (value !== "X") {
targetSheet.getRange(parseInt(i) + 1, column).setValue(value);
column++;
}
}
}
}
See how the target sheet is set as a variable instead of using a range on the source sheet — it gives you more readability and freedom
It seems that when iterating like for (var i in row) i is considered to be a string so the parseInt call
column variable is needed to make sure there are no empty cells in the target sheet
I've also changed sheet.getRange(1,1,15) to sheet.getDataRange() to make sure your code gets all the data in the sheet
The approach of setting values into single cells separately is not optimal. It should work for you in your case as the data range seems pretty small but as soon as you get to hundreds and thousand of rows, you'll need to switch to setValues, so you'll need to build a 2D-array before pasting the values. The tricky thing is that your resulting rows may have a variable number of items (depending on how many Xs are in a row) while setValues expects all the rows to be of the same length — it's possible to get round it of course.

Remove duplicates across multiple sheets

I want to remove duplicates across 2 different sheets.
I have my active sheet, and I want to remove duplicates that already exist in my sheet "Blacklist". I want to run this process for both Column A and Column B (or simply for any values across the entire sheets). When a duplicate is found, I want to leave the row in tact but replace the value with '' (e.g. an empty cell).
I have a working version I mangled together, but only for the active sheet.
N.B. it's the findDuplicate function that I use, the removeDuplicate function I left there not to mess anything up :)
// this is a Google Apps Script project
function onOpen() {
var spreadsheet = SpreadsheetApp.getActive();
var menuItems = [
{ name: 'Find duplicates...', functionName: 'findDuplicate' },
{ name: 'Remove duplicates...', functionName: 'removeDuplicate' }
];
spreadsheet.addMenu('Duplicates', menuItems);
}
function removeDuplicate() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveRange();
var data = range.getValues();
var rowNum = range.getRow();
var columnNum = range.getColumn();
var columnLength = data[0].length;
var uniqueData = [];
var duplicateData = [];
// iterate through each 'row' of the selected range
// x is
// y is
var x = 0;
var y = data.length;
// when row is
while (x < y) {
var row = data[x];
var duplicate = false;
// iterate through the uniqueData array to see if 'row' already exists
for (var j = 0; j < uniqueData.length; j++) {
if (row.join() == uniqueData[j].join()) {
// if there is a duplicate, delete the 'row' from the sheet and add it to the duplicateData array
duplicate = true;
var duplicateRange = sheet.getRange(
rowNum + x,
columnNum,
1,
columnLength
);
duplicateRange.deleteCells(SpreadsheetApp.Dimension.ROWS);
duplicateData.push(row);
// rows shift up by one when duplicate is deleted
// in effect, it skips a line
// so we need to decrement x to stay in the same line
x--;
y--;
range = sheet.getActiveRange();
data = range.getValues();
// return;
}
}
// if there are no duplicates, add 'row' to the uniqueData array
if (!duplicate) {
uniqueData.push(row);
}
x++;
}
}
function findDuplicate() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveRange();
var data = range.getValues();
var rowNum = range.getRow();
var columnNum = range.getColumn();
var columnLength = data[0].length;
var uniqueData = [];
// iterate through each 'row' of the selected range
for (var i = 0; i < data.length; i++) {
var row = data[i];
var duplicate = false;
// iterate through the uniqueData array to see if 'row' already exists
for (var j = 0; j < uniqueData.length; j++) {
if (row.join() == uniqueData[j].join()) {
// if there is a duplicate, highlight the 'row' from the sheet
duplicate = true;
var duplicateRange = sheet.getRange(
rowNum + i,
columnNum,
1,
columnLength
);
duplicateRange.setValue('');
}
}
// if there are no duplicates, add 'row' to the uniqueData array
if (!duplicate) {
uniqueData.push(row);
}
}
}
Thanks so much for your help! I've been at this for a few hours and figured I should just ask the experts for advice :)
The first lines of both your removeDuplicate and findDuplicate function seems indeed to indicate that you refer to the active spreadsheet / sheet / range
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveRange();
var data = range.getValues();
If you want to be able to use the same function for a given spreadsheet / sheet / range which is not the active one, you will need to use other functions than the getActiveXXX().
For example, to get the sheet named "Blacklist", you should use
sheet = spreadsheet.getSheetByName("Blacklist")
(see also https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet#getsheetbynamename)
If you want to access a specific range which differs from the active range, you should use the getRange method (see also https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet#getrangea1notation)
Note that getRange method can be used in different ways, e.g.
getRange("A1:D4"), getRange(1, 1, 3, 3) (the parameters being respectively startRow, startColumn, numRows,numColumns)
Additionally, if you don't want to hardcode the last line of your 2 columns, you will most probably need this function to find the last line in the code :
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet#getlastrow
(there is also an example there showing how to use getRange() in combination with getLastRow()).
I hope this will help you going further.
Please note that I didn't check the rest of your code and just assumed that your deduplication logic works fine as you mentioned it in your commment.
Good luck !

how can I create batch in javascript loop to select x Rows then next x Rows until all rows are done?

I have a google sheet with some data and I am trying to combine all cell data in a JSON variable so I can pass it on to API to do something.
I have this javascript function that takes all data and combine everything in JSON variable like this:
function combine_val() {
var startRow = 2; // First row of data to process. Starting with 2 to ignore headers
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.
// DO something HERE with "data" to push the JSON string in a controlled batch to API
SpreadsheetApp.getUi().alert(data);
}
The API where I am passing this data takes JSON with 200 rows only. So I need help in creating a batch of 200.
This is what I have done so far and need help.
var mybatch = 200;
function combine_val_increment() {
var startRow = 2; // First row of data to process. Starting with 2 to ignore headers
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.
for (var i = 0; i < numRows/mybatch; ++i) {
var dataRange = mysheet.getRange(startRow, startColumn, startRow+mybatch, 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.
// DO something HERE with "data" to push the JSON string in a controlled batch to API
SpreadsheetApp.getUi().alert(data);
startRow = startRow + mybatch;
}
}
Approach# 2 based on suggestions / comments
function rowsForAPI2(){
var batchsize = 2;
//var batchsize = 200;
//var ss = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.getActive().getSheetByName('Sheet5'); //SHEET NAME
// var data = ss.getDataRange().getValues(); // 2D array with all of the data in the sheet.
var startRow = 2; // First row of data to process. Skip 1st row of column headers for this test.
var startColumn = 1; //First Column to process, in case that changes.
var numRows = ss.getLastRow(); // Number of rows to process
//var numCols = mysheet.getLastColumn(); //Also the number of columns to process, again in case that changes.
var numCols = 4; //Hardcode for this test
var dataRange = ss.getRange(startRow, startColumn, numRows, numCols);//Get the full range of data in the sheet dynamically.
var data = dataRange.getValues();//Get the value of the range, AND convert it to a JSON string in one line.
var rowCount = ss.getLastRow() - 1; // To know how many rows have data (-1 will ignore the column header)
var obj = [];
var temp = 0;
var results = [];
Logger.log(rowCount/batchsize)
for (var i = 0; i < (rowCount/batchsize); i++){
for (var j = temp; j < batchsize*(i+1); j++){
obj.push(data[j]); // Push row into object.
temp = j;
if (temp == rowCount-1) // Got to the end of the data.
break;
}
temp++;
results.push(JSON.stringify(obj)); // Adds the JSON object to an array
obj = []; // Clear the array of the 200 rows stored
}
return results;
}
function doSomething(){
var objects = rowsForAPI2();
var curr;
for ( var i = 0; i < objects.length; i++){
curr = objects[i];
// Do the API thing with curr...
Logger.log(curr);
}
}
New requirement for approach 3 -
In this new use-case, instead of passing data in JSON.stringify array of 200 rows batch. I have an API endpoint that takes rows in this format:
{
"recipient": {
"emailAddress": "email_1#domain.com",
"listName": {
"path": "testfolder"
}
}
},
{
"recipient": {
"emailAddress": "email_2#domain.com",
"listName": {
"path": "testfolder"
}
}
},
{
"recipient": {
"emailAddress": "email_3#domain.com",
"listName": {
"path": "testfolder"
}
}
}
How can I use same solution discussed below with batching technique but for building the above^ formatted records where email list is coming from values in rows in google sheet? Any help?
Try this:
function rowsForAPI(){
var ss = SpreadsheetApp.getActiveSheet();
var data = ss.getDataRange().getValues(); // 2D array with all of the data in the sheet.
var rowCount = ss.getLastRow(); // To know how many rows have data
var obj = []; // Array where the row objects will be stored
var temp = 0; // A counter of how many rows have been processed.
var results = []; // Array where the resulting JSON objects will be stored and returned.
Logger.log(rowCount/200)
for (var i = 0; i < (rowCount/200); i++){
for (var j = temp; j < 200*(i+1); j++){
obj.push(data[j]); // Push row into object.
temp = j;
if (temp == rowCount-1) // Got to the end of the data (if there are less than 200 rows in this batch).
break;
}
temp++; // Update row count.
results.push(JSON.stringify(obj)); // Adds the JSON object to an array
obj = []; // Clear the array of the 200 rows stored before the next loop starts.
}
return results;
}
function doSomething(){
var objects = rowsForAPI();
var curr;
for ( var i = 0; i < objects.length; i++){ // Go through each batch
curr = objects[i]; // Current batch.
// Do the API thing with curr...
}
}
This method will return an array of JSON objects that holds batches of 200 rows from the Sheet, it will also stop if it reaches the end of the data in the sheet.

Categories