Google Apps Script - Find Row number based on a cell value - google-apps-script

I'm looking for some assistance on find the row number for a cell that contains a specific value.
The spreadsheet has lots of data across multiple rows & columns. I'm looping over the .getDataRange and can located the cell containing the value I'm looking for. Once found I'd like to know the row that his cell is in, so that I can further grab additional cell values since I know exactly how many rows down and/or columns over the additional information is from the found cell.
Here is a bit of the code for finding the cell containing a specific string.
function findCell() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++) {
var row = "";
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] == "User") {
row = values[i][j+1];
Logger.log(row);
}
}
}
}
When outputting Logger.log(row) it gives me the values I'm looking for. I want to determine what row each value is in, so I can then go down X number of rows and over X columns to get the contents of other cells.

I don't know much about google apps but it looks like [i] is your row number in this circumstance.
So if you did something like:
function findCell() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++) {
var row = "";
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] == "User") {
row = values[i][j+1];
Logger.log(row);
Logger.log(i); // This is your row number
}
}
}
}

This method finds the row number in a particular column for a given value:
function rowOf(containingValue, columnToLookInIndex, sheetIndex) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets();
var dataRange = sheet[sheetIndex].getDataRange();
var values = dataRange.getValues();
var outRow;
for (var i = 0; i < values.length; i++)
{
if (values[i][columnToLookInIndex] == containingValue)
{
outRow = i+1;
break;
}
}
return outRow;
}
But if you do it like that, it won't refresh unless you change any of the parameters, that's because a custom function should be deterministic, that is, for the same parameters, it should always give the same result.
So instead, it's better to do it this way:
function rowOf(containingValue, range) {
var outRow = null;
if(range.constructor == Array)
{
for (var i = 0; i < range.length; i++)
{
if(range[i].constructor == Array && range[i].length > 0)
{
if (range[i][0] == containingValue)
{
outRow = i+1;
break;
}
}
}
}
return outRow;
}
In this case, you need to pass the full column range your looking for like so:
rowOf("MyTextToLookFor", 'MySheetToLookIn'!A1:A)
Where you would replace A by the colum of your choice, and MySheetToLookIn by your sheet's name and MyTextToLookFor by the text you are looking for.
This will allow it to refresh on adding rows and removing rows.

Since 2018 this can be done without a loop using flat().
function findRow(searchVal) {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var columnCount = sheet.getDataRange().getLastColumn();
var i = data.flat().indexOf(searchVal);
var columnIndex = i % columnCount
var rowIndex = ((i - columnIndex) / columnCount);
Logger.log({columnIndex, rowIndex }); // zero based row and column indexes of searchVal
return i >= 0 ? rowIndex + 1 : "searchVal not found";
}

ES6 gave us a one liner for this (but expanded for full detail).
function findRow() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss. getActiveSheet(); // OR GET DESIRED SHEET WITH .getSheetByName()
const dataRange = sheet.getDataRange();
const values = dataRange.getValues();
const columnIndex = 3 // INDEX OF COLUMN FOR COMPARISON CELL
const matchText = "User"
const index = values.findIndex(row => row[columnIndex] === matchText)
const rowNumber = index + 1
return rowNumber
}

This can be done with the Text Finder:
function findRow(searchValue){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var thisSheet = ss.getActiveSheet();
var tf = thisSheet.createTextFinder(searchValue)
var thisRow = tf.findNext().getRow()
return thisRow
}
Discovered through this post: Google App Scripts find text in spreadsheet and return location index
Implementing: https://developers.google.com/apps-script/reference/spreadsheet/text-finder

To find a row in a Google Sheets sheet based on the value of a specific column (in this case, the invoice_id column) and edit another column (in this case, the send column) in that row, you can use the following script:
// Replace "Sheet1" with the name of your sheet
var sheet = SpreadsheetApp.getActive().getSheetByName("Sheet1");
// Replace "A" with the column letter for the invoice_id column
// and "B" with the column letter for the send column
var invoiceIdColumn = "A";
var sendColumn = "B";
// Replace "12345" with the invoice_id you want to search for
var invoiceIdToSearch = "12345";
// Find the row number of the row with the matching invoice_id
var data = sheet.getDataRange().getValues();
var row = data.findIndex(row => row[invoiceIdColumn - 1] == invoiceIdToSearch) + 1;
// If a row with the matching invoice_id was found
if (row) {
// Set the value of the send column in that row to "true"
sheet.getRange(row, sendColumn).setValue(true);
}
This script will search through the entire sheet for a row with an invoice_id value that matches the invoiceIdToSearch variable, and then set the value of the send column in that row to true.
Note that this script assumes that the invoice_id column is the first column in the sheet (column A) and the send column is the second column (column B). If your columns are in different positions, you will need to adjust the invoiceIdColumn and sendColumn variables accordingly.

Related

How to loop through cells in Google Sheets and log information if specific variables exist

You will need this link to solve this: - https://docs.google.com/spreadsheets/d/11PjVSWPfqOBPSej3AlQ-_T8Bws66kevR08Q2NRTmVcE/edit?usp=sharing
So this is a tricky one. I am looking to loop through the name in Sheet1 Column A, and also loop through the type in Column C. Then if Column A in Sheet1 matches the name of column A in Sheet2, AND the type is "pickup" I want to log the other information that is in Sheet 1 inside of Column C on Sheet 2. Log the things like "State", "Location", "City" next to the identical name in Sheet 2. I hope that makes sense.
I know I probably need to use a loop within a loop to do all this, but this is as far as I can get as I cannot figure out how to write the loop and then log the rows where the 2 names match.
function myFunction() {
function pullDeliveryNotes()
{
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var sheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2");
var dataRange = sheet.getDataRange();
var dataRange2 = sheet2.getDataRange();
var rng = sheet.getRange(2,1, dataRange.getLastRow()-1,dataRange.getLastColumn());
var rng2 = sheet.getRange(2,3, dataRange.getLastRow()-1,dataRange.getLastColumn());
var rng3 = sheet2.getRange(2,1, dataRange2.getLastRow()-1,dataRange2.getLastColumn());
var rngA = rng.getValues().toString()
var rngB = rng2.getValues().toString()
var rngC = rng3.getValues().toString()
for(var i = 0; i < rng.length; i++) {
for (var x = 0; x < rng2.length; x++) {
for (var y = 0; y < rng3.length; y++) {
if (rng[i][x][y].includes(rng3[i][x][y]) && rng2[i][x][y] === "pickUp") {
Logger.log("We got it")
}
}
}
}
}
}
Based on your problem statement.
Try this sample script:-
function checkValues() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const ssSource = ss.getSheetByName('Sheet1')
const ssTarget = ss.getSheetByName('Sheet2')
const sourceRange = ssSource.getDataRange().getValues().filter(r=> r[2] === 'Pickup'); // Filtering only pickup values
const targetRange = ssTarget.getDataRange().getValues();
for(var i = 0 ; i < sourceRange.length ; i++)
{
for(var j = 1 ; j < targetRange.length ; j++)
{
if(sourceRange[i][0] === targetRange[j][0]) // if name matches
{
ssTarget.getRange(`C${j+1}`).setValue(`${sourceRange[i][3]},${sourceRange[i][4]},${sourceRange[i][5]}`) // set values in column C
}
}
}
}

Google Sheets - Two-way lookup pulling data from multiple sheet tabs

I've been working on a sheet where I want to have and input tab and output tab.
The input tab is basically a table and the output tab will be a calendar (of sorts).
In my example:
Sheet1 = input tab
Sheet2 = output tab
I wish to have a vlookup that will pull its search_key from Sheet2 (output) and search it against a range in Sheet1.
I've been messing around with the following (green cell Sheet2 in example):
=IFERROR(VLOOKUP(A2,Sheet1!$A$2:$C$7,MATCH(B1,Sheet1!$A$2:$C$7,0),False))
I had also tried a variation of this using hlookup instead of MATCH but didn't have much luck with it.
The problem I have is that I no longer know where to place my column index. In my example sheet I have it working with a one way vlookup (blue cell Sheet2) that returns the desired value from Sheet1 (Length Column) using this index. Is it not possible to do so in the two way lookup??
Here's a link to the example:
https://docs.google.com/spreadsheets/d/1_nqH-XOxNhQAUVJzesNBZeMci7AV9RowSQUnptAruPc/edit?usp=sharing
Try running this function in Apps Script:
function myFunction() {
var ss = SpreadsheetApp.getActive();
var sheet1 = ss.getSheetByName('Sheet1');
var sheet2 = ss.getSheetByName('Sheet2');
var firstRow = 2;
var numRows = sheet1.getLastRow() - 1;
var firstCol = 1;
var numCols = sheet1.getLastColumn();
var inputData = sheet1.getRange(firstRow, firstCol, numRows, numCols).getValues();
var numBrands = sheet2.getLastRow();
var outputRange = sheet2.getDataRange();
var outputData = outputRange.getValues();
// Iterating through each row in Sheet1 data:
for(var i = 0; i < numRows; i++) {
// Iterating through each row in Sheet2:
for(var j = 1; j < outputData.length; j++) {
// Iterates through each cell for each row in Sheet2.
for(var k = 1; k < outputData[0].length; k++) {
var inputBrand = inputData[i][0];
var outputBrand = outputData[j][0];
var inputDate = inputData[i][1];
var outputDate = outputData[0][k];
// It checks whether the date and brand corresponding to each cell
// (same row or column) matches the date and brand in the current
// row in Sheet1
if(inputBrand == outputBrand && inputDate.toString() == outputDate.toString()) {
var inputLength = inputData[i][2];
sheet2.getRange(j+1, k+1, 1, 1).setValue(inputLength);
}
}
}
}
}

Find and write to next blank cells in a column using App maker

I need help with finding a number from a specific row in column A and write next to its blank cells in two different columns of the same row. e.g Find a number in COLUMN A2, Write to cells in COLUMN B2 and C2.
I have currently used filters and it hasn't helped.
function filterRange(filteredRange) {
return filteredRange == [2.0];
}
function findCellByValue() {
var ss = getSpreadSheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++) {
var range = values[i];
var filtered = range.filter(filterRange);
var newValue = filtered.concat(["Tee", "uhuuu"]);
console.log(newValue);
var ssheet = sheet.getRange("B2:C2");
console.log(ssheet.setValues([newValue]));
}
}
Anyways I have managed to find a workaround it though it is not the best possible solution but for this, it works.
function findCellByValue(cellValue) {
console.log(cellValue);
var ss = getSpreadSheet().getActiveSheet();
var range = ss.getRange("A2:A").getValues();
for(var i = 0; i < range.length; i ++)
{
if(range[i] == cellValue)
{
var Arrayvalues = range[i].concat(["test1","test2"]);
ss.getRange(i+2,1,1,3).setValues([Arrayvalues]);
i = range.length;
}
}

Google Apps Script for Removing Rows Containing Part of a Keyword

I want to delete the rows on Google Sheets that contain employees, tests, irrelevant submissions, and duplicate entries. My code needs to be standalone so that it can be used across my workplace.
Specifically, I want to:
Remove any rows that contain an email address belonging to a certain organization (ex: any email address that ends in #domainname.com). I've been using a piece of code to delete rows containing three specific email addresses belonging to my coworkers, but I was hoping to find a way to delete all employees in one sweep without coding in each individual email. Here's the code I've been using:
function delVtlEm() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var rowsDeleted = 0;
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (row[1] == 'isaac#domainname.com' ||
row[1] == 'danni#domainname.com' ||
row[1] == 'georgia#domainname.com') {
sheet.deleteRow((parseInt(i) + 1) - rowsDeleted);
rowsDeleted++;
}
}
}
Remove any rows that contain the word "login" from a comment section where "login" might be only part of the copy in that column. For example, someone might fill out a Contact Us form and ask in the comment section for help with their login info - but this isn't a qualified lead for my purposes. Their message may be "Hey, can you help me with my login?" or some other similar phrasing, which is why I want to delete any row containing "login" in any capacity.
Please let me know if you have any ideas or suggested code!
I have implemented the following smartDelete() function based on your code.
This function allows you to achieve the following,
Identify any number of domains (in badDomains array) to delete its corresponding rows.
Identify any number of words (in badWords array) to delete its corresponding rows.
Both of the two search criteria above are case-insensitive; you can change that by changing the regular expression modifier (stored in regExpModifiers) to "" or Null.
Actions above can be taken on three different columns (stored in fnameColumnNumber, emailColumnNumber and companyColumnNumber)
Let me know if you face any issues or have any feedback.
function smartDelete() {
// smartDelete settings goes here,
var badDomains = ["vtldesign\\.com", "parterreflooring\\.com"];
var badWords = ["Vital", "Parterre", "test"];
var fnameColumnNumber = 0;
var emailColumnNumber = 1;
var companyColumnNumber = 3;
var regExpModifiers = "i";
// Gain access data in the sheet
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var rowsDeleted = 0;
var deleteAction = false;
// delete procedure
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
deleteAction = false;
// check bad words
for (var j = 0; j <= badWords.length - 1; j++) {
var myPattern = new RegExp(badWords[j], regExpModifiers);
var status = row[fnameColumnNumber].toString().match(myPattern);
if (status) {
// match found, mark this row for delete
deleteAction = true;
break;
};
};
// check bad domains
for (var j = 0; j <= badDomains.length - 1; j++) {
var myPattern = new RegExp(badDomains[j], regExpModifiers);
var status = row[emailColumnNumber].toString().match(myPattern);
if (status) {
// match found, mark this row for delete
deleteAction = true;
break;
};
};
// check bad words
for (var j = 0; j <= badWords.length - 1; j++) {
var myPattern = new RegExp(badWords[j], regExpModifiers);
var status = row[companyColumnNumber].toString().match(myPattern);
Logger.log(status)
if (status) {
// match found, mark this row for delete
deleteAction = true;
break;
};
};
// execute delete.
if (deleteAction) {
sheet.deleteRow((parseInt(i) + 1) - rowsDeleted);
rowsDeleted++;
};
};
}
You can use indexOf('what to find') to look for a partial string. Also, don't delete rows in the Sheet individually. That is inefficient. Delete elements (rows) from the array, clear the sheet tab, and then set all the new values.
function delVtlEm() {
var i,row;
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var rowsDeleted = 0;
var arrayOfStringsToFind = ["whatToLookFor","whatToLookFor2","whatToLookFor3"];
for (i = 0; i <= numRows - 1; i++) {
row = values[i];
column1Value = row[0];//Get the value of column A for this row
column2Value = row[1];
column3Value = row[2];
if (arrayOfStringsToFind.indexOf('column1Value') !== -1) {
values.splice(i,1);//
}
if (column2Value.indexOf('#vtldesign.com') !== -1) {
values.splice(i,1);//Remove one element in the data array at index i
}
if (column3Value.indexOf('whatToLoookFor') !== -1) {
values.splice(i,1);//
}
}
sheet.clearContents();//clear the contents fo the sheet
sheet.getRange(1, 1, values.length, values[0].length);//Set new values
}

Deleting rows in google sheets using Google Apps Script

I encountered the weirdest error while trying to delete rows that match a specific value using Google Apps Script.
Here is my Code:
function myFunction() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("file.csv");
var values = sheet.getRange("N2:N").getValues();
var row_del = new Array();
for(var i=0;i<values.length;i++)
{
if(values[i] == 'del'){
row_del.push(i+2); // This line was added for debugging purposes.
// sheet.deleteRow(i+2) was the line that was in this condition
// (i+2) is used because row 1 has headers and the range starts from 0.
}
}
// Logger.log(row_del);
// GmailApp.sendEmail("my_email_address", "subject", row_del)
for (var i = 0; i < row_del.length; i++)
{
sheet.deleteRow(row_del[i]);
}
}
The code that I have written picks up the row numbers that should be deleted but not all these rows are deleted in my first try. I should execute my script a number of times for these rows to be deleted.
If my code has an error, it should show up and if the logic is wrong, incorrect rows must be deleted. I encounter neither of these scenarios and I should just execute this function multiple times.
Is there something that I'm missing here?
When a row is deleted from a sheet, the rows below it get renumbered even as the script continues to run. If the script subsequently tries to also delete those rows, the result is unpredictable. For this reason, when deleting rows one should proceed from bottom to top. In your case, like so:
for (var i = row_del.length - 1; i>=0; i--) {
sheet.deleteRow(row_del[i]);
}
refer to lock function suggested by google team:
var lock = LockService.getScriptLock();
lock.waitLock(30000); // lock 30 seconds
//do whatever you want here
lock.releaseLock();
That way, you got your deleting job work once at a time! The system thread won't go on to other jobs until 30 seconds is up or releasing the lock.
google dev-document: https://developers.google.com/apps-script/reference/lock/lock
To delete blank rows from a single named sheet, assuming column 1 has data in valid rows.
Search and delete from highest row number to lowest row number.
// Deletes any row whose first column is blank
// WARNING: Assumes any valid row has data in column 1
function deleteBlankRows() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("Sheet3");
var lastRow = SpreadsheetApp.getActiveSheet().getLastRow();
for (var i = lastRow; i > 0; i--) {
var range = sheet.getRange(i,1);
var data = range.getValue();
if (data == '') {
sheet.deleteRow(i);
}
}
}
increment i only if you didn't delete a row
function del_F_rows(){
var i=1;
while(!sht_balanceHistory.getRange(i,1).isBlank()){
if(sht_balanceHistory.getRange(i,3).getValue()=="F")
sht_balanceHistory.deleteRow(i);
else
i=i+1;
}
}
You can just delete the rows alter the last row using the deleteRows function like this:
var maxRows = newsheet.getMaxRows();
var lastRow = newsheet.getLastRow();
if (maxRows-lastRow != 0)
{
newsheet.deleteRows(lastRow+1, maxRows-lastRow);
}
Update 2020
A faster and a more modern JavaScript approach would be to use forEach and reverse() to iterate backwards.
It makes more sense to flatten the values array since it concerns data of a single column.
Solution:
function myFunction() {
const doc = SpreadsheetApp.getActiveSpreadsheet();
const sheet = doc.getSheetByName("file.csv");
const values = sheet.getRange("N2:N").getValues().flat();
values.reverse().forEach((r,i)=>{
if (r=='del'){
sheet.deleteRow(values.length-i+1);
}
});
}
So, is this what it should look like?
function myFunction() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("Sheet1");
var values = sheet.getRange("A:A").getValues();
var row_del = new Array();
for(var i=0;i<values.length;i++)
{
if(values[i] == 'N'){
row_del.push(i+2); // This line was added for debugging purposes.
// sheet.deleteRow(i+2) was the line that was in this condition
// (i+2) is used because row 1 has headers and the range starts from 0.
}
}
// Logger.log(row_del);
// GmailApp.sendEmail("my_email_address", "subject", row_del)
for (var i = row_del.length - 1; i>=0; i--) { sheet.deleteRow(row_del[i]); }
}
Copy pasting from: https://gist.github.com/dDondero/285f8fd557c07e07af0e
Instead of looping through the rows twice, you can count how mwny rows have been deleted, to calculate the correct index for the row that you will delete next.
function deleteRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var rowsDeleted = 0;
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (row[0] == 'delete' || row[0] == '') { // This searches all cells in columns A (change to row[1] for columns B and so on) and deletes row if cell is empty or has value 'delete'.
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
};
This is a script I used to delete everything but certain values, but can be easiliy modified to keep certain strings and runs a lot faster than looping through all the data filters out and deletes the values you want to remove and bulk deletes. my data had about 10000 rows so loop would have taken forever.
function DeleteCertainRows(){
columnCopy("D","Z","YourSheetName");//Copy from your target column in this case D to another column in this case Z
replaceInSheet("Z","YourSheetName","keep String 1","keep");//find and replace the value with what you want to keep
replaceInSheet("Z","YourSheetName","keep String 2","keep");//Can repeat for additional values
DeleteValueInColumn("Z","YourSheet","keep");//filters and deletes all other values Column is case sensitive and cant go past Z
};
function replaceInSheet(repColumn,sheetname, to_replace, replace_with) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetname);
var values = sheet.getRange(repColumn+'1:'+repColumn+sheet.getMaxRows());
var textFinder = values.createTextFinder(to_replace);
var replaceall = textFinder.replaceAllWith(replace_with);
};
function columnCopy(copyfrm,copyto,sheetname){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetname);
var copyrange = sheet.getRange(copyfrm+'1:'+copyfrm+sheet.getMaxRows());
var pasterange = sheet.getRange(copyto+'1:'+copyto+sheet.getMaxRows());
copyrange.copyTo(pasterange);
};
function DeleteValueInColumn(colStr, sheetname, deleteval){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetname);
var filterRange=sheet.getDataRange();
var myFilter = filterRange.createFilter();
var CriteriaBuild = SpreadsheetApp.newFilterCriteria();
var Criteria = CriteriaBuild.whenTextDoesNotContain(deleteval);//change to whenTextContains to delete your string value instead of everything else
var myCriteria = Criteria.build();
var str = colStr;
var myCol = parseInt(str.charCodeAt(0) - 64);
Logger.log(myCol);
myFilter.setColumnFilterCriteria(myCol, myCriteria);
var deleterange=sheet.getRange('2:'+sheet.getLastRow());
sheet.deleteRows(deleterange.getRow(), deleterange.getNumRows());
myFilter.remove();
};
Another method is to sort records and delete all at once.
My following code will sort the data with N/A value and will delete all rows at once.
function DeleteErrorLine2() {
var SS = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ORDER SHEET"); //Get Open Lines Sheet
var lastRow = SS.getLastRow();
var range = SS.getRange(2, 1, lastRow-1, 33); //get range
range.sort({column: 2, ascending: false}) // filter data descending
var cell = SS.getRange('B1'); // values stored in cell B
var ct = 1; // starting row
while(cell.offset(ct, 0).getValue() == "#N/A" ) {
ct++;
}
if(ct!=1){
ct = ct - 1 // minus 1 to get the last row
SS.deleteRows(2, ct)
}
range.sort({column: 2, ascending: true}) // filter data again ascending
}