Result in show in column - javascript

It does not show the result in the column, and I am always informed of this error: Exception: Range not found myfunction # Código.gs:12. I don't understand where the problem is. I tried several methods and failed. I changed the code several times and I couldn't. I searched a lot on the site, but I didn't find a solution. I tried to solve it and I didn't find something to solve this problem.
function minhaFuncao() {
// Gets the active worksheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Gets the name of the tab where the result will be written
var sheetName = "TERÇA";
// Gets the tab with the specified name
var sheet = ss.getSheetByName(sheetName);
// Defines a cell range that includes all desired tabs
var range = ss.getRange("JANEIRO!A5:C,FEVEREIRO!A5:C,'MARÇO'!A5:C,ABRIL!A5:C,MAIO!A5:C,JUNHO!A5:C,JULHO!A5:C,AGOSTO!A5:C,SETEMBRO!A5:C,OUTUBRO!A5:C,NOVEMBRO!A5:C,DEZEMBRO!A5:C");
// Gets values ​​from cell range
var data = range.getValues();
// Defines the query to be executed
var query = "select Col1 where dayOfWeek(Col3) = 3";
// Run the query and get the results
var queryResults = SpreadsheetApp.newFilter().setRange(range).whenFormulaSatisfied(query).getRange().getValues();
// Creates a regular expression from the value in cell A2 on the worksheet
var regex = new RegExp("^" + sheet.getRange("A2").getValue() + "$");
// Reduce the array of values ​​into a single sum by filtering out values ​​that match the regular expression
var count = queryResults.flat().filter(cell => regex.test(cell)).reduce((acc, val) => acc + (val === "" ? 0 : Number(val)), 0);
// Sets the result to the sum of matching values ​​or an empty string if no values ​​match
var result = count > 0 ? count : "";
// Gets the "TERÇA" tab of the worksheet
var tuesdaySheet = ss.getSheetByName("TERÇA");
// Sets cells A2 to A100 in the "TERÇA" tab as the result
tuesdaySheet.getRange("A2:A100").setValue(result);
}

function justastart() {
const ss = SpreadsheetApp.getActive();
const names = ["JANEIRO", "FEVEREIRO", "MARÇ", "ABRIL", "MAIO", "JUNHO", "JULHO", "AGOSTO", "SETEMBRO", "OUTUBRO", "NOVEMBRO", "DEZEMBRO"];
const sh = ss.getSheetByName("TERÇA");
let arr = ss.getSheets().filter(sh => ~names.indexOf(sh.getName())).map(sh => sh.getRange("A5:C" + sh.getLastRow()).getValues());
//arr is an array of 2d arrays with all of your data
}

Related

How to loop through each column in a given row and then have the loop go automatically to the next column

I need to extract a data from this other sheet where the header will repeat itself by the number of items under it. So far I can only do this with the first column from the data source and I can’t seem to get the right code where the loop will go through the next column once it detects the first empty cell of the previous column.
Here’s where I am getting stuck
function filter(){
var sheet = SpreadsheetApp.getActiveSpreadsheet()
var data = sheet.getSheetByName("data")
var filter = sheet.getSheetByName("filter")
var dataRange = data.getDataRange().getValues();
var dataLr = data.getLastRow();
var dataLc = data.getLastColumn();
var row = 1
var col = 1
for(var i=1; i < dataRange.length; i++){
let targetAppName = filter.getRange(i+1,1)
let targetQname = filter.getRange(i+1,2)
targetQname.setValue(dataRange[i])
targetAppName.setValue(dataRange[0][0])
}
}
I’m trying to get these results in another sheet tab:
|Names | Subjects |
|:—————|:———————-:|
|Michael Lowry| Trigonometry|
|Michael Lowry| Biology|
And so on, until it reads all columns and rows of the data range
Loop throught each column of each row
function loopthroughteachcolumnofarow() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
const vs = sh.getDataRange().getValues();
vs.forEach((r,i) => {
r.forEach((c,j) => {
//c is the value for vs[i][j];
})
})
}

Google Sheet App Script: Match 1 values from one sheet to another sheet and then if condition met set background

I'm new to app script and still learning how to do thing. Here's what I want to do: I have 2 sheets, in the 1st are just the names ('List') in columns based on each person language, the 2nd one is schedule ('Roster'). I want to change cell background into green the cell with person name when person is on schedule from 8-5.
Any help or idea is very appreciated. Thank you in advance.
Here's what I tried so far to do:
function addShiftColor() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var actSheet = ss.getSheetByName('List');
var rosSheet = ss.getSheetByName('Roster');
var lastCol = actSheet.getLastColumn() +1;
var lastRow = actSheet.getLastRow() +1;
var end = rosSheet.getLastRow();
for (var column = 1; column < lastCol; column++) {
for (var row = 1; row < lastRow; row++) {
var cellget = actSheet.getRange(row, column).getValue();
var cellset = actSheet.getRange(row, column);
for(i=1, j=5; i <= end, j <= end; i++,j++){
var cell1 = rosSheet.getRange(i, 1).getValue();
var cell2 = rosSheet.getRange(j, 253).getValue();
if(cell1 === cellget && cell2 === "08 -- 17"){
cellset.setBackground("green");
}
}
}
}
}
2nd Sheet 'Roster'
1st Sheet 'List'
This is the result I want to achieve.
Here is the link to the file https://docs.google.com/spreadsheets/d/1wfSEQtqZJ3XEPla_KRBp7v56sPV5wlciHEHA7IS0MaI/edit?usp=sharing
Thank you!
In your situation, how about the following sample script?
Sample script:
function myFunction() {
// 1. Retrieve 2 sheets.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const listSheet = ss.getSheetByName("List");
const rosterSheet = ss.getSheetByName("Roster");
// 2. Retrieve values.
const listRange = listSheet.getRange(2, 1, listSheet.getLastRow() - 1, listSheet.getLastColumn());
const [, , dates, , ...rosterValues] = rosterSheet.getDataRange().getDisplayValues();
// 3. Create an object for searching names.
const today = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "dd MMM");
const col = dates.indexOf(today);
if (col == -1) {
throw new Error(`Date of ${today} was not found.`);
}
const obj = rosterValues.reduce((o, r) => {
if (r[0]) o[r[0]] = r[col];
return o;
}, {});
// 4. Create an array for changing the background color of cells.
const colors = listRange.getValues().map((r, i) => r.map((c, j) => obj[c] && obj[c] == "08 -- 17" ? "green" : null));
// 5. Change the background color of cells.
listRange.setBackgrounds(colors);
}
The flow of this script is as follows.
Retrieve 2 sheets.
Retrieve values.
Create an object for searching names.
Create an array for changing the background color of cells.
Change the background color of cells.
Testing:
When this script is run to your sample Spreadsheet, the following result is obtained.
From:
To:
Note:
In this sample, green is used as the background color. This is from your script. If you want to change this, please modify "green".
This sample script is for your sample Spreadsheet. So, when the structure of the Spreadsheet is changed, this script might not be able to be used. So, please be careful abuot this.
References:
reduce()
map()
setBackgrounds(color)
Added:
About your following additional question,
I have a question, If I would like to add as well different colors for 11-20 and 17-20, what should be changed in the script?
In this case, how about the following sample script?
Sample script:
function myFunction() {
const colorObj = { "08 -- 17": "green", "17 -- 02": "blue", "11 -- 20": "red" }; // Please modify this for your actual situation.
// 1. Retrieve 2 sheets.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const listSheet = ss.getSheetByName("List");
const rosterSheet = ss.getSheetByName("Roster");
// 2. Retrieve values.
const listRange = listSheet.getRange(2, 1, listSheet.getLastRow() - 1, listSheet.getLastColumn());
const [, , dates, , ...rosterValues] = rosterSheet.getDataRange().getDisplayValues();
// 3. Create an object for searching names.
const today = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "dd MMM");
const col = dates.indexOf(today);
if (col == -1) {
throw new Error(`Date of ${today} was not found.`);
}
const obj = rosterValues.reduce((o, r) => {
if (r[0]) o[r[0]] = r[col];
return o;
}, {});
// 4. Create an array for changing the background color of cells.
const keys = Object.keys(colorObj);
const colors = listRange.getValues().map((r, i) => r.map((c, j) => obj[c] && keys.includes(obj[c]) ? colorObj[obj[c]] : null));
// 5. Change the background color of cells.
listRange.setBackgrounds(colors);
}

Trying to paste Values from formula Google App Script

This is just a snippet of my code from Google App Script which iterates through each row in columns 1, 2, 3. If an edit is made in column 3, an incremental ID will be generated and a concatenation of the same row and different columns will also be generated - in this case Column D, E, and F. I am struggling with figuring out a way to change the formulas into values. What am I missing here?
// Location format = [sheet, ID Column, ID Column Row Start, Edit Column]
var locations = [
["Consolidated Media Plan",1,9,3]
];
function onEdit(e){
// Set a comment on the edited cell to indicate when it was changed.
//Entry data
var range = e.range;
var col = range.getColumn();
var row = range.getRow();
// Location Data
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
function getNewID(){
function IDrange(){
var dataRange = sheet.getDataRange();
var lastRow = dataRange.getLastRow();
return sheet.getRange(IDrowStart,IDcol,lastRow-IDrowStart).getValues();
};
//Get largest Value in range
function getLastID(range){
var sorted = range.sort();
var lastIDval = sorted[sorted.length-1][0];
return lastIDval;
};
//Stores leading letters and zeroes and trailing letters
function getLettersNzeroes(id){
//Get any letters or zeroes.
var re = new RegExp("^([a-zA-Z0])$");
var letterZero = [];
for(char = 0; char < id.length; char++){
if(re.test(id[char])){
letterZero.push([char,id[char]]);// [[position, letter or zero]]
};
};
// Categorize letters and zeroes into start and end blocks
var startLetterZero = "",
endLetter = "",
len = letterZero.length - 1;
for(j = 0; j < letterZero.length; j++){
if(letterZero[j][0] === j){
startLetterZero += letterZero[j][1];
}else if(letterZero[j][1] !== "0" && letterZero[len][0] - (len - j) == letterZero[j][0]){
endLetter += letterZero[j][1];
};
};
var startNend = {"start":startLetterZero,"end":endLetter};
return startNend;
};
//Gets last id number. Adds 1 an checks to set if its new length is greater than the lastNumber.
function getNewNumber(id){
var removeZero = false;
var lastNum = parseInt(id.replace(/\D/g,''),10);//Remove letters
var newNum = (lastNum+1).toString();
if(lastNum.toString().length !== newNum.length){
var removeZero = true;
};
var newNumSet = {"num":newNum, "removeZero": removeZero};
return newNumSet
};
var lastID = getLastID(IDrange());
var lettersNzeroes = getLettersNzeroes(lastID);
var newNumber = getNewNumber(lastID);
//If the number is 9,99,999,9999 etc we need to remove a zero if it exists.
if(newNumber.removeZero === true && lettersNzeroes.start.indexOf("0") !== -1.0){
lettersNzeroes.start = lettersNzeroes.start.slice(0,-1);
};
//Rejoin everything together
var newID = lettersNzeroes.start +
newNumber.num +
lettersNzeroes.end;
return newID;
};
for(i = 0; i < locations.length; i++){
var sheetID = locations[i][0],
IDcol = locations[i][1],
IDrowStart = locations[i][2],
EditCol = locations[i][3];
var offset = IDcol - EditCol;
var cell = sheet.getActiveCell();
if(sheetID === sheet.getName()){
if(EditCol === col){
//ID Already Exists the editing cell isn't blank.
if(cell.offset(0,offset).isBlank() && cell.isBlank() === false){
var newID = getNewID();
cell.offset(0,offset).setValue(newID);
cell.offset(0,-1).setFormulaR1C1('=concatenate(R[0]C[-1],"_",INDEX(Glossary!K:K,MATCH(R[0]C[2],Glossary!J:J,0)))');
};
};
};
};
};
EDIT:
This is my full code, I have been unsuccessful with trying to retrieve just the values of the formula within the same (i.e, If C9 gets edited, a formula with the values specific to the 9th row should be populated)
Also, I've tried to add an index/match formula to the concatenation formula at the bottom of the code - it works as expected on the google sheets, but when I run it with the script it pastes the correct formula but it returns a #NAME? error message. However, when I copy and paste the exact same formula in the cell, it works perfectly, any idea what could be causing this error?
This works for me. I know it's not exactly the same thing but I didn't have access to getNewId()
function onEdit(e) {
var sh=e.range.getSheet();
if(sh.getName()!='Sheet1')return;
//e.source.toast('flag1');
if(e.range.columnStart==3 && e.range.offset(0,1).isBlank() && e.value) {
//e.source.toast('flag2');
e.range.offset(0,1).setValue(e.value);
e.range.offset(0,2).setFormulaR1C1('=concatenate(R[0]C[-1],"_",R[0]C[-2],"_",R[0]C[-3],"_",R[0]C[-4])');
}
}

How to retrieve data from columns dependent upon date

I have a rota (a fixed order of rotation (as of persons or duties)) that I've already had help with this week. It's up & running as is, but for simpler reading I'd like to transpose it.
You can see the transposed sheet as I'd like it here
The current script is for the pre-transposed table.
It would search Column 0 for the date. If it was was 7 days away it would retrieve the name from Column 1 & match it with e-mail address in separate sheet etc.
What I'd like to do is instead have the Date in Row 0 & then subsequent names in Row 1 etc etc
I've tried various things. I've stepped through the code & can see what it's doing, & I've done some reading through about 2 dimensional arrays, but I can't seem to find a way of getting the code to work down through columns, instead of across the rows.
Here's the code:
function sendEmails() {
var ss1 = SpreadsheetApp.getActiveSpreadsheet();
var sh1 = ss1.getSheetByName("Rota")
ss1.setActiveSheet(sh1);
var rotalink = "https://docs.google.com/spreadsheets/d/1LgzUWSAGA2kbpar8r5nosU1bSHF7nrtvtUiHS3nB_e8";
var sheet = SpreadsheetApp.getActiveSheet();
// Fetch the range
var dataRange = sheet.getRange("B3:G50")
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
var today=new Date();
var timecell = new Date(row[0]);
var timediff = new Date();
var one_day=1000*60*60*24;
var daystogo = Math.ceil((timecell.getTime()-today.getTime())/(one_day));
if (daystogo==7) {//only e-mail people with one week to go. To change that alter the "7" to the number of days you want
var subject = "Rota reminder!";
var emailAddress = [];
var message;
message = "Hello \n\n"+
"You are down to help at Youth Café this week. \n\n" +
"Please see the below rota for your role \n\n" +
"If you have any questions or problems let us know at thameyouthcafe#gmail.com \n\n" +
"Remember, you can check the rota anytime by clicking on the link below: \n\n"+
rotalink
for (var x = 1; x < 5; x++) { // 5 because emails are till col4
// var emailAddress = []; // Start by collecting the non-blank emails in an array
if (getEmailFromName(row[x]) != "") {
emailAddress.push(getEmailFromName(row[x]))
}
}
emailAddress = emailAddress.join(); // Join the array to get a comma separated string
MailApp.sendEmail(emailAddress, subject, message);
}
}
}
and here's the getEmailFromName function that matches with SKey (which I presume comes from the "i" variable in the first function?
function getEmailFromName(sKey) {
// to use this function, don’t put anything in the first column (A) or row (1).
// Put the name (i.e. the key, or what we’re looking for) in column B.
// Put what we want to return in column C.
var columnToSearch = 1; //column B
// Set the active sheet to our email lookup
var ss1 = SpreadsheetApp.getActiveSpreadsheet();
var sh1 = ss1.getSheetByName("EmailContactList")
ss1.setActiveSheet(sh1);
var data = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
var line = -1;
for( var i = 0; i < data.length; i++ ) {
if( data[i][columnToSearch] == sKey ) {
line = i;
break;
}
}
if( line != -1 ) {
//do what you want with the data on "line"
return data[line][2]; //value on column C of the matched line
}
else {
return "";
// if criteria is not found
}
}
Try it this way:
function sendEmails() {
var ss1 = SpreadsheetApp.getActive();
var sh1 = ss1.getSheetByName("Rota")
ss1.setActiveSheet(sh1);
var rotalink = "https://docs.google.com/spreadsheets/d/1LgzUWSAGA2kbpar8r5nosU1bSHF7nrtvtUiHS3nB_e8";
var sheet = SpreadsheetApp.getActiveSheet();
var dataRange = sheet.getRange("B3:G50")
var data = dataRange.getValues();
for (var i=0;i<dataRange.length;i++) {
var row = data[i];
var today=new Date();
var timecell = new Date(row[0]);
var timediff = new Date();
var one_day=1000*60*60*24;
var daystogo = Math.ceil((timecell.getTime()-today.getTime())/(one_day));
if (daystogo==7) {
var subject = "Rota reminder!";
var emailAddress = [];
var message = Utilities.formatString('Hello\n\nYou are down to help at Youth Café this week.\n\n Please see the below rota for your role \n\nIf you have any questions or problems let us know at thameyouthcafe#gmail.com \n\nRemember, you can check the rota anytime by clicking on the link below: \n\n%s',rotalink);
for (var x=1;x<5;x++) {
if(data[i][x]) {
emailAddress.push(data[i][x]);
}
}
MailApp.sendEmail(emailAddress.join(), subject, message);
}
}
}
Managed to solve it - thank you for your contributions. Turned out it was incredibly simple.
Just had to change this line:
var timecell = new Date(data[0])
to this:
var timecell = new Date(data[0][i])
so it iterates through the first row of each column.

Google Script - get last row of specific column [duplicate]

I have a sheet with data in cols A through H.
I need to determine the last row in column A that contains data (it's all contiguous - no gaps in the data/rows).
There is also data in the other columns that have more rows of data than column A, so I need to isolate only column A. (And/or just a range within col A).
I can do this on the spreadsheet level using
=COUNTA(A2:A100)
However in all of my researching for a Google Apps Script solution, all I seem to find are requirements to perform multiple functions encompassing dozens of lines of code - including plenty of i++ stuff... Which I could do less complexly via offsetting directly from A1.
Is there possibly a column-specific way of modifying this method?
var aLast = ss.getDataRange().getNumRows();
If a convoluted process is what is required, then so be it. But I find it difficult to imagine (and even more difficult to find!) a simpler solution.
Does anyone care to enlighten me (or pop my bubble)?
How about using a JavaScript trick?
var Avals = ss.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
I borrowed this idea from this answer. The Array.filter() method is operating on the Avals array, which contains all the cells in column A. By filtering on a native function's constructor, we get back only non-null elements.
This works for a single column only; if the range contains multiple columns,then the outcome of filter() will include cells from all columns, and thus be outside the populated dimensions of the range.
This will get the last row in a sheet assuming based on column A.
function getLastDataRow(sheet) {
var lastRow = sheet.getLastRow();
var range = sheet.getRange("A" + lastRow);
if (range.getValue() !== "") {
return lastRow;
} else {
return range.getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
}
}
This fixes #mrityunjay-pandey partially-correct answer.
To extend this answer to get the last row and column, we can use:
function columnToLetter(column) {
var temp, letter = '';
while (column > 0) {
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}
function letterToColumn(letter) {
var column = 0, length = letter.length;
for (var i = 0; i < length; i++) {
column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1);
}
return column;
}
function getLastDataColumn(sheet) {
var lastCol = sheet.getLastColumn();
var range = sheet.getRange(columnToLetter(lastCol) + "1");
if (range.getValue() !== "") {
return lastCol;
} else {
return range.getNextDataCell(SpreadsheetApp.Direction.PREVIOUS).getColumn();
}
}
function getLastDataRow(sheet) {
var lastRow = sheet.getLastRow();
var range = sheet.getRange("A" + lastRow);
if (range.getValue() !== "") {
return lastRow;
} else {
return range.getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
}
}
function run() {
var sheet = SpreadsheetApp.getActiveSheet();
var [startRow, lastRow] = [2, getLastDataRow(sheet)];
var [startCol, lastCol] = [1, getLastDataColumn(sheet)];
}
Although there is no straighforward formula, I can think of, it doesn't require dozens of lines of code to find out the last row in column A. Try this simple function. Use it in a cell the normal way you'd use some other function =CountColA()
function CountColA(){
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
for(var i = data.length-1 ; i >=0 ; i--){
if (data[i][0] != null && data[i][0] != ''){
return i+1 ;
}
}
}
var Direction=SpreadsheetApp.Direction;
var aLast =ss.getRange("A"+(ss.getLastRow()+1)).getNextDataCell(Direction.UP).getRow();
As mentioned by lopezvit,
According to the documentation, getNextDataCell is similar to go to the given range and pressing ctrl (command) + the arrow key. So this works because it goes to the las possible row, adds one (arrow down) and then ctrl + UP, so it will definitely get the last row with some content. The only thing that could be improved is to check if last cell + 1 is greater than max cell, and make an specific logic for that case.
Update 2021 - Considers also empty cells
The accepted answer as well as most of the answers (if not all of them) have one common limitation which might not be the case for the owner of the question (they have contiguous data) but for future readers.
Namely, if the selected column contains empty cells in between, the accepted answer would give the wrong result.
For example, consider this very simple scenario:
the accepted solution would give 4 while the correct answer is 6.
Solution:
Find the index of first non-empty value starting from the end of the array by using the reverse method.
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet1')
const lrow = sh.getLastRow();
const Avals = sh.getRange("A1:A"+lrow).getValues();
const Alast = lrow - Avals.reverse().findIndex(c=>c[0]!='');
Although I don't know whether this is a good method, how about this method? This method doesn't use the loops. Please check this as one of samples.
Retrieve the column data that you want to know the number of last row.
Import the column data to a new spreadsheet as a temporary sheet. (In this case, you can also add a new sheet to the spreadsheet you currently use and it can be used as a temporary.)
Retrieve the number of last row using getLastRow().
Remove the temporary spreadsheet.
Sample Script :
var col = ##; // Here, please input a column number.
var ss = SpreadsheetApp.getActiveSheet();
var coldata = ss.getRange(1, col, ss.getLastRow(), 1).getValues();
var tempss = SpreadsheetApp.create("temporary_sheet");
tempss.getActiveSheet().getRange(1,1,coldata.length, coldata[0].length).setValues(coldata);
var last_row = tempss.getLastRow(); // The number of last row
Drive.Files.remove(tempss.getId()); // In this case, the file is removed using Drive API.
Note :
In above case, the number of last row can be retrieved, even if the column has null cells. If the column has no null cells, you can retrieve the number of last row for a specific column by following script. This doesn't create a temporary sheet.
var last_row = ss.getRange(1, col, ss.getLastRow(), 1).getValues().filter(String).length;
Updated at May 19, 2021:
In this case, I would like to approach with the following 2 patterns.
Retrieving 1st empty cell of specific column by searching from TOP of sheet
Retrieving 1st NON empty cell of specific column by searching from BOTTOM of sheet
In order to achieve above, I think that the following 2 patterns, can be used.
Retrieve the values of the column and search the result using the loop.
Retrieve directly the result using the built-in methods of Google Apps Script.
I measured the process cost of them. As the result, it was found that the following 2 scripts are lowest of all methods.
1. Retrieving 1st empty cell of specific column by searching from TOP of sheet
Object.prototype.get1stEmptyRowFromTop = function (columnNumber, offsetRow = 1) {
const range = this.getRange(offsetRow, columnNumber, 2);
const values = range.getDisplayValues();
if (values[0][0] && values[1][0]) {
return range.getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow() + 1;
} else if (values[0][0] && !values[1][0]) {
return offsetRow + 1;
}
return offsetRow;
};
// Please run this function.
function main() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
const res = sheet.get1stEmptyRowFromTop(3);
console.log(res); // Retrieve the 1st empty row of column "C" by searching from TOP of sheet.
}
Please give the sheet object and column number.
In this script, when 2nd argument is used, you can set the offset row. For example, when the 1st and 2nd rows are the header rows, you can use this script as const res = sheet.get1stEmptyRowFromTop(3, 2);.
2. Retrieving 1st NON empty cell of specific column by searching from BOTTOM of sheet
In this question, I thought that this pattern might be suitable.
Object.prototype.get1stNonEmptyRowFromBottom = function (columnNumber, offsetRow = 1) {
const search = this.getRange(offsetRow, columnNumber, this.getMaxRows()).createTextFinder(".").useRegularExpression(true).findPrevious();
return search ? search.getRow() : offsetRow;
};
// Please run this function.
function main() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
const res = sheet.get1stNonEmptyRowFromBottom(3);
console.log(res); // Retrieve the 1st non empty row of column "C" by searching from BOTTOM of sheet.
}
Please give the sheet object and column number.
In this script, when 2nd argument is used, you can set the offset row. For example, when the 1st and 2nd rows are the header rows, you can use this script as const res = sheet.get1stNonEmptyRowFromBottom(3, 2);.
Result:
When above script is used, the following result is obtained.
When the script of "Retrieving 1st empty cell of specific column by searching from TOP of sheet" is used for the column "C", the row 6 is obtained.
When the script of "Retrieving 1st NON empty cell of specific column by searching from BOTTOM of sheet" is used for the column "C", the row 9 is obtained.
Reference:
Benchmark: Process Costs for Retrieving 1st Empty Cell and 1st Non Empty Cell of Specific Column in Google Spreadsheet using Google Apps Script
In this report, you can see the detail data of the benchmark of each method.
Never too late to post an alternative answer I hope. Here's a snippet of my Find last Cell. I'm primarily interested in speed. On a DB I'm using with around 150,000 rows this function took (average) 0.087 seconds to find solution compared to #Mogsdad elegant JS solution above which takes (average) 0.53 sec on same data. Both arrays were pre-loaded before the function call. It makes use of recursion to do a binary search. For 100,000+ rows you should find it takes no more than 15 to 20 hops to return it's result.
I've left the Log calls in so you can test it in the console first and see its workings.
/* #OnlyCurrentDoc */
function myLastRow() {
var ss=SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var colArray = ss.getRange('A1:A').getDisplayValues(); // Change to relevant column label and put in Cache
var TestRow=ss.getLastRow();
var MaxRow=ss.getMaxRows();
Logger.log ('TestRow = %s',TestRow);
Logger.log ('MaxRow = %s',MaxRow);
var FoundRow=FindLastRow(TestRow,MaxRow);
Logger.log ('FoundRow = %s',FoundRow);
function FindLastRow(v_TestRow,v_MaxRow) {
/* Some housekeeping/error trapping first
* 1) Check that LastRow doesn't = Max Rows. If so then suggest to add a few lines as this
* indicates the LastRow was the end of the sheet.
* 2) Check it's not a new sheet with no data ie, LastRow = 0 and/or cell A1 is empty.
* 3) A return result of 0 = an error otherwise any positive value is a valid result.
*/
return !(colArray[0][0]) ? 1 // if first row is empty then presume it's a new empty sheet
:!!(colArray[v_TestRow][0]) ? v_TestRow // if the last row is not empty then column A was the longest
: v_MaxRow==v_TestRow ? v_TestRow // if Last=Max then consider adding a line here to extend row count, else
: searchPair(0,v_TestRow); // get on an find the last row
}
function searchPair(LowRow,HighRow){
var BinRow = ((LowRow+HighRow)/2)|0; // force an INT to avoid row ambiguity
Logger.log ('LowRow/HighRow/BinRow = %s/%s/%s',LowRow, HighRow, BinRow);
/* Check your log. You shoud find that the last row is always found in under 20 hops.
* This will be true whether your data rows are 100 or 100,000 long.
* The longest element of this script is loading the Cache (ColArray)
*/
return (!(colArray[BinRow-1][0]))^(!(colArray[BinRow][0])) ? BinRow
: (!(colArray[BinRow-1][0]))&(!(colArray[BinRow][0])) ? searchPair(LowRow,BinRow-1)
: (!!(colArray[BinRow-1][0]))|(!!(colArray[BinRow][0])) ? searchPair(BinRow+1,HighRow)
: false; // Error
}
}
/* The premise for the above logic is that the binary search is looking for a specific pairing, <Text/No text>
* on adjacent rows. You said there are no gaps so the pairing <No Text/Text> is not tested as it's irrelevant.
* If the logic finds <No Text/No Text> then it looks back up the sheet, if it finds <Text/Text> it looks further
* down the sheet. I think you'll find this is quite fast, especially on datasets > 100,000 rows.
*/
You can do this by going in the reverse way.
Starting from the last row in spreadsheet and going up till you get some value. This will work in all the cases even if you have some empty rows in between.
Code looks like below:
var iLastRowWithData = lastValue('A');
function lastValue(column) {
var iLastRow = SpreadsheetApp.getActiveSheet().getMaxRows();
var aValues = SpreadsheetApp.getActiveSheet().getRange(column + "2:" + column + lastRow).getValues();
for (; aValues[iLastRow - 1] == "" && iLastRow > 0; iLastRow--) {}
return iLastRow;
}
I've used getDataRegion
sheet.getRange(1, 1).getDataRegion(SpreadsheetApp.Dimension.ROWS).getLastRow()
Note that this relies on the data being contiguous (as per the OP's request).
Old thread but I have found a simple way that seems to work
ws.getRange("A2").getNextDataCell(SpreadsheetApp.Direction.DOWN).getLastRow()
You can also use the following code:
function findTheLastRow(){
var ui = SpreadsheetApp.getUi();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var range = sheet.getRange("B1:B").getValues();
var filtered_r = range.filter(String).length;
ui.alert("Column B's last cell is number: " + filtered_r + " and its value is: " + range[filtered_r - 1][0]);
}
This script counts the amount of cells that have a value in a column, so the cells above the last cell needs to have a value in order to get the right result.
To get the number of columns or last column's index:
var numColumns = sheet.getLastColumn()
To get the no of rows or last row's index:
var numRows = sheet.getLastRow()
where
var sheet = SpreadsheetApp.getActiveSheet()
For very large spreadsheets, this solution is very fast:
function GoLastRow() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A:AC').createFilter();
var criteria = SpreadsheetApp.newFilterCriteria().whenCellNotEmpty().build();
var rg = spreadsheet.getActiveSheet().getFilter().setColumnFilterCriteria(1, criteria).getRange();
var row = rg.getNextDataCell (SpreadsheetApp.Direction.DOWN);
LastRow = row.getRow();
spreadsheet.getActiveSheet().getFilter().remove();
spreadsheet.getActiveSheet().getRange(LastRow+1, 1).activate();
};
This seems to work:
function myFunction() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('B1').activate();
spreadsheet.getCurrentCell().getNextDataCell(SpreadsheetApp.Direction.DOWN).activate();
var LastDataRow = spreadsheet.getCurrentCell().getRowIndex();
Logger.log(LastDataRow);
};
After a while trying to build a function to get an integer with the last row in a single column, this worked fine:
function lastRow() {
var spreadsheet = SpreadsheetApp.getActiveSheet();
spreadsheet.getRange('B1').activate();
var columnB = spreadsheet.getSelection().getNextDataRange(SpreadsheetApp.Direction.DOWN).activate();
var numRows = columnB.getLastRow();
var nextRow = numRows + 1;
}
This worked for me:
var ss = SpreadsheetApp.openById(YourSpreadsheetID);
var main_sheet = ss.getSheetByName(YourMainSheet);
main_sheet.getRange('K16').activate(); // substitute your cell from where you want to count
main_sheet.getCurrentCell().getNextDataCell(SpreadsheetApp.Direction.DOWN).activate();
var last_row_submissions = main_sheet.getCurrentCell().getRowIndex();
This may be another way to go around lastrow.
You may need to play around with the code to suit your needs
function fill() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('a1').activate();
var lsr = spreadsheet.getLastRow();
lsr=lsr+1;
lsr="A1:A"+lsr;
spreadsheet.getActiveRange().autoFill(spreadsheet.getRange(lsr), SpreadsheetApp.AutoFillSeries.DEFAULT_SERIES);
};
I have changed Tanaike's answer a bit. This version creating a sheet instead of spreadsheet.
var col = 1; // Here, please input a column number, in this case it is the number of A column(1).
var ss = SpreadsheetApp.getActiveSheet();
var coldata = ss.getRange(1, col, ss.getLastRow(), 1).getValues();
var tempss = SpreadsheetApp.getActiveSpreadsheet().insertSheet("temporary_sheet");
tempss.getRange(1,1,coldata.length, coldata[0].length).setValues(coldata);
var last_row = tempss.getLastRow(); // The number of last row
SpreadsheetApp.getActiveSpreadsheet().deleteSheet(SpreadsheetApp.getActiveSpreadsheet().getSheetByName("temporary_sheet"));
personally I had a similar issue and went with something like this:
function getLastRowinColumn (ws, column) {
var page_lastrow = ws.getDataRange().getNumRows();
var last_row_col = 0
for (i=1; i<=page_lastrow;i++) {
if (!(spread.getRange(column.concat("",i)).isBlank())) {last_row_col = i};
}
return last_row_col
}
It looks for the number of rows in the ws and loops through each cell in your column. When it finds a non-empty cell it updates the position of that cell in the last_row_col variable. It has the advantage of allowing you to have non-contiguous columns and still know the last row (assuming you are going through the whole column).
I tried to write up 3 following functions, you can test them for different cases of yours. This is the data I tested with:
Function getLastRow1 and getLastRow2 will return 0 for column B
Function getLastRow3 will return 1 for column B
Depend on your case, you will tweak them for your needs.
function getLastRow1(sheet, column) {
var data = sheet.getRange(1, column, sheet.getLastRow()).getValues();
while(typeof data[data.length-1] !== 'undefined'
&& data[data.length-1][0].length === 0){
data.pop();
}
return data.length;
}
function test() {
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet6');
Logger.log('Cách 1');
Logger.log("Dòng cuối cùng của cột A là: " + getLastRow1(sh, 1));
Logger.log("Dòng cuối cùng của cột B là: " + getLastRow1(sh, 2));
Logger.log("Dòng cuối cùng của cột C là: " + getLastRow1(sh, 3));
Logger.log("Dòng cuối cùng của cột D là: " + getLastRow1(sh, 4));
Logger.log("Dòng cuối cùng của cột E là: " + getLastRow1(sh, 5));
Logger.log('Cách 2');
Logger.log("Dòng cuối cùng của cột A là: " + getLastRow2(sh, 1));
Logger.log("Dòng cuối cùng của cột B là: " + getLastRow2(sh, 2));
Logger.log("Dòng cuối cùng của cột C là: " + getLastRow2(sh, 3));
Logger.log("Dòng cuối cùng của cột D là: " + getLastRow2(sh, 4));
Logger.log("Dòng cuối cùng của cột E là: " + getLastRow2(sh, 5));
Logger.log('Cách 3');
Logger.log("Dòng cuối cùng của cột A là: " + getLastRow3(sh, 'A'));
Logger.log("Dòng cuối cùng của cột B là: " + getLastRow3(sh, 'B'));
Logger.log("Dòng cuối cùng của cột C là: " + getLastRow3(sh, 'C'));
Logger.log("Dòng cuối cùng của cột D là: " + getLastRow3(sh, 'D'));
Logger.log("Dòng cuối cùng của cột E là: " + getLastRow3(sh, 'E'));
}
function getLastRow2(sheet, column) {
var lr = sheet.getLastRow();
var data = sheet.getRange(1, column, lr).getValues();
while(lr > 0 && sheet.getRange(lr , column).isBlank()) {
lr--;
}
return lr;
}
function getLastRow3(sheet, column) {
var lastRow = sheet.getLastRow();
var range = sheet.getRange(column + lastRow);
if (range.getValue() !== '') {
return lastRow;
} else {
return range.getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
}
}
I realise this is quite an old thread but it's one of the first results when searching for this problem.
There's a simple solution to this which afaik has always been available... This is also the "recommended" way of doing the same task in VBA.
var lastCell = mySheet.getRange(mySheet.getLastRow(),1).getNextDataCell(
SpreadsheetApp.Direction.UP
);
This will return the last full cell in the column you specify in getRange(row,column), remember to add 1 to this if you want to use the first empty row.
This is what worked for me:
var ss = SpreadsheetApp.openById(YourSpreadsheetID);
var main_sheet = ss.getSheetByName(YourMainSheet);
main_sheet.getRange('K16').activate(); // substitute your cell from where you want to count
main_sheet.getCurrentCell().getNextDataCell(SpreadsheetApp.Direction.DOWN).activate();
var last_row_submissions = main_sheet.getCurrentCell().getRowIndex();
This is the best way for me, get the reference column and then get the last row
var ssm = SpreadsheetApp.openById(id).getSheetByName(name);
var lastRow = ssm.getRange('A2').getNextDataCell(SpreadsheetApp.Direction.DOWN).offset(1, 0).getRow();
ssm.getRange(lr, 1, 1, 8).setValues([data]);
An update of Mogsdad's solution:
var Avals = ss.getRange("A1:A").getValues();
var Alast = Avals.filter(function(r){return r[0].length>0});
The best solution depends on how many rows your sheet has and the type of the data. I've done a comparison and benchamark of various proposed solutions. Most work only on plain data and fail if the data has gaps, formulae, array fourmulae, importranges, local references, filter or query functions and more. Which is pretty much always.
The always accurate solution that works reasonably fast on all data types is the Reversed for + getValues() one. Use this if you want no headaches:
// replace 'yourSheetName' and column 1 with your values
var tab = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('yourSheetName')
var column = tab.getRange('A:A')
// get lastFilledRow
var value = ''
const max = tab.getMaxRows()
var values = column.getValues()
values = [].concat.apply([], values)
for (row = max - 1; row > 0; row--) {
value = values[row]
if (value != '') { break }
}
var lastFilledRow = row + 1
If you really want a one liner and you are certain your data has no local references, use the getNextDataCell() solution, it’s fast and simple
// replace 'yourSheetName' and column 'A' with your values
var tab = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('yourSheetName')
var column = tab.getRange('A' + tab.getMaxRows())
// get lastFilledRow
var lastFilledRow = column.getNextDataCell(SpreadsheetApp.Direction.UP).getA1Notation().slice(1)
If you want to see the full analysis and benchmark results, go here
My method takes a flat array of the column values, reverses it, searches for the index of the first defined cell.
Subtract that index from the total length of the values array to get the non reversed index, and then add any rows that may precede the column values to return the actual last row of that column on the spreadsheet.
const colValues = WORKSHEET.getRange(STARTROW, STARTCOLUMN, WORKSHEET.getLastRow(), 1)
.getValues()
.flat();
const getLastRowOfCol = (colValues, startRow = 1) => {
return (colValues.length - colValues.reverse().findIndex(cell => !!cell)) + startRow - 1;
};
For my use case, I needed to get the last row of column 5 after row 13.
const ss = SpreadsheetApp.openById(mySpreadsheetId);
const ws = ss.getSheetByName('myWorkSheetName');
const colValues = ws.getRange(14, 5, ws.getLastRow(), 1)
.getValues()
.flat()
const colLastRow = getLastRowOfCol(colValues, 13)
I am using one single line code to get last row have data in column B (Index 2) as follow:
var tmp3 = pointDataSheet.getRange(pointDataSheet.getLastRow() + 1,2).getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
I rewrote the getLastRow/getLastColumn functions to specify a row index or column index.
function get_used_rows(sheet, column_index){
for (var r = sheet.getLastRow(); r--; r > 0) {
if (sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn()).getCell(r, column_index).getValue() != ""){
return r;
break;
}
}
}
function get_used_cols(sheet, row_index){
for (var c = sheet.getLastColumn(); c--; c > 0) {
if (sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn()).getCell(row_index, c).getValue() != ""){
return c;
break;
}
}
}
Here's an alternative way of solving this. It uses a while loop but takes into consideration empty gaps between rows.
function getLastRow (column) {
var iLastRow = ss.getActiveSheet().getMaxRows();
var aValues = ss.getActiveSheet().getRange(column + ":" + column).getValues();
var row = "";
while(row == ""){
row = aValues[iLastRow-1];
iLastRow--;
}
return iLastRow;
}
I am using getDataRange() followed by getNumRows(). The first function
Returns a Range corresponding to the dimensions in which data is present
and the second function
Returns the number of rows in this range.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ws = ss.getActiveSheet();
var lastRow = ws.getDataRange().getNumRows();
P.S I hope this works for all cases.

Categories

Resources