Filter Data and Remove Rows Fast while keeping Header - performance

This is my final task. I am looking to filter data from column 17, that if anything in that column is less than 1, to remove the data in those rows. There is data that gets imported daily and their are a lot of rows that I want to filter fast and then delete those rows specified. I just want to keep the value of 1 in column 17. There is a frozen header at the top that does not move. What I have works but is extremely slow. Thank you for any help!
function filterData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh1 = ss.getSheetByName('CancelRawData');
var colCdata = sh1.getRange(1,17,sh1.getLastRow(),1).getValues()
var filtered = colCdata.filter(String);
Logger.log(filtered);
for(let i = colCdata.length-1;i >= 0;i--){
if(colCdata[i] <1 === true){
sh1.deleteRow(i+1)
}
}
SpreadsheetApp.getUi().alert("🎉 Congratulations, your data has been filtered!", SpreadsheetApp.getUi().ButtonSet.OK);
}

In your situation, in order to reduce the process cost of your script, how about the following modifications?
Modified script 1:
In this modification, Sheets API is used. Before you use this script, please enable Sheets API at Advanced Google services.
function filterData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh1 = ss.getSheetByName('CancelRawData');
var colCdata = sh1.getRange(1, 17, sh1.getLastRow(), 1).getValues();
var sheetId = sh1.getSheetId();
var requests = colCdata.reduce((ar, [q], i) => {
if (!isNaN(q) && q < 1) ar.push({ deleteDimension: { range: { sheetId, startIndex: i, endIndex: i + 1, dimension: "ROWS" } } });
return ar;
}, []).reverse();
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
SpreadsheetApp.getUi().alert("🎉 Congratulations, your data has been filtered!", SpreadsheetApp.getUi().ButtonSet.OK);
}
Modified script 2:
In this modification, retrieved all values are filtered, and the filtered values are put on the sheet.
function filterData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh1 = ss.getSheetByName('CancelRawData');
var range = sh1.getDataRange();
var values = range.getValues().filter(r => isNaN(r[16]) || r[16] >= 1);
range.clearContent();
sh1.getRange(1, 1, values.length, values[0].length).setValues(values);
SpreadsheetApp.getUi().alert("🎉 Congratulations, your data has been filtered!", SpreadsheetApp.getUi().ButtonSet.OK);
}
References:
Method: spreadsheets.batchUpdate
DeleteDimensionRequest

Related

Dependent Data Validation column script not rejecting input (Apps Script)

I made the following script to have a dependent dropdown data validation list for an entire column, however I can't seem to find a way to reject input if it's not valid, even with setAllowInvalid(), which I suspect is because I'm doing an offset.
Any help would be greatly appreciated.
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Cost Centers");
var ui = SpreadsheetApp.getUi();
var activeCell = ss.getActiveCell();
if(activeCell.getColumn() == 4 && activeCell.getRow() > 1) {
if(activeCell.isBlank()){
activeCell.offset(0,1).clearContent().clearDataValidations();
}
var departments = data.getRange(3,26,1,20).getValues();
var departmentIndex = departments[0].indexOf(activeCell.getValue()) + 26;
if(departmentIndex != 0) {
var validationRange = data.getRange(4,departmentIndex,60);
var validationRule = SpreadsheetApp.newDataValidation().requireValueInRange(validationRange).setAllowInvalid(true).build();
activeCell.offset(0,1).setDataValidation(validationRule);
} }
}
setAllowInvalid(false)
setAllowInvalid(allowInvalidData)
true if the rule should allow input that fails data validation; false if not.

How to make this script run in all tabs except certain tabs (Sheets)?

I'm a total newbie when it comes to scripts and I honestly don't really use it often at all but I thought it'd be fun to automatize an alphabetical order sorting I wanted to do and so I used this script:
/** Build a menu item
From https://developers.google.com/apps-script/guides/menus#menus_for_add-ons_in_google_docs_or_sheets
**/
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createMenu('Sort');
if (e && e.authMode == ScriptApp.AuthMode.NONE) {
// Add a normal menu item (works in all authorization modes).
menu.addItem('Sort Sheet', 'sort');
} else {
// Add a menu item based on properties (doesn't work in AuthMode.NONE).
var properties = PropertiesService.getDocumentProperties();
var workflowStarted = properties.getProperty('workflowStarted');
if (workflowStarted) {
menu.addItem('Sort Sheet', 'sort');
} else {
menu.addItem('Sort Sheet', 'sort');
}
menu.addToUi();
}
}
function sort() {
/** Variables for customization:
Each column to sort takes two variables:
1) the column index (i.e. column A has a colum index of 1
2) Sort Asecnding -- default is to sort ascending. Set to false to sort descending
**/
//Variable for column to sort first
var sortFirst = 3; //index of column to be sorted by; 1 = column A, 2 = column B, etc.
var sortFirstAsc = true; //Set to false to sort descending
//Variables for column to sort second
var sortSecond = 1;
var sortSecondAsc = true;
//Number of header rows
var headerRows = 1;
/** End Variables for customization**/
/** Begin sorting function **/
var activeSheet = SpreadsheetApp.getActiveSheet();
var sheetName = activeSheet.getSheetName(); //name of sheet to be sorted
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var range = sheet.getRange(headerRows+1, 1, sheet.getMaxRows()-headerRows, sheet.getLastColumn());
range.sort([{column: sortFirst, ascending: sortFirstAsc}, {column: sortSecond, ascending: sortSecondAsc}]);
}
It worked very well but I wondered if there was a way to not have it work in two specific tabs of the same sheets?
I believe your goal as follows.
You want to execute the script of sort for the sheets except for the specific sheets.
In this case, how about declaring the excluded sheet names and checking the current sheet using the excluded sheet names? When this is reflected to your script, it becomes as follows.
Modified script:
In this case, please set the sheet names you want to exclude to excludeSheetNames. At sample script, when the active sheet is "Sheet1" and "Sheet2", the script below the if statement is not run.
function sort() {
var excludeSheetNames = ["Sheet1", "Sheet2"]; // <--- Added
var sortFirst = 3;
var sortFirstAsc = true;
var sortSecond = 1;
var sortSecondAsc = true;
var headerRows = 1;
var activeSheet = SpreadsheetApp.getActiveSheet();
var sheetName = activeSheet.getSheetName();
if (excludeSheetNames.includes(sheetName)) return; // <--- Added
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var range = sheet.getRange(headerRows + 1, 1, sheet.getMaxRows() - headerRows, sheet.getLastColumn());
range.sort([{ column: sortFirst, ascending: sortFirstAsc }, { column: sortSecond, ascending: sortSecondAsc }]);
}
For example, if you want to run the script below the if statement for only excludeSheetNames, please modify if (excludeSheetNames.includes(sheetName)) return; to if (!excludeSheetNames.includes(sheetName)) return;.
Reference:
includes()

Google Apps Script that loops through a filter and sends an e-mail with a PDF?

I have data from a questionnaire (20K rows) that I need to share with the store managers (report) of our shops (400 shops). I managed to write a script that sends a pdf of my sheet to a list of e-mail addresses. But I'm stuck on writing the loop for the filter, since I can't get the setVisibleValues(values) function to work for FilterCriteriaBuilder. The setHiddenValues(values) function works, but I can't figure out how to combine that with the loop.
Sample of my Google Sheet
See below for my current code:
/**
* Filtersheet by location
*/
function FilterSheet() {
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data')
spreadsheet.getRange('F1').activate();
var criteria = SpreadsheetApp.newFilterCriteria()
.setHiddenValues(['Amsterdam, Rotterdam'])
.build();
spreadsheet.getFilter().setColumnFilterCriteria(6, criteria);
};
/**
* Send pdf of currentspreadsheet
*/
function SendPdf() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Adres');
var blob = DriveApp.getFileById(ss.getId()).getAs("application/pdf");
blob.setName(ss.getName() + ".pdf");
var startRow = 2; // First row of data to process
var numRows = 2; // Number of rows to process
// Fetch the range of cells A2:B3
var dataRange = spreadsheet.getRange(startRow, 1, numRows, 2);
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var i in data) {
var row = data[i];
var emailAddress = row[0]; // First column
var message = 'I hearby send you the overview of your data'
var subject = 'Overview of data';
MailApp.sendEmail(emailAddress, subject, message,{
attachments:[blob]});
}
}
getValues() returns the values of all range's cells no matter if they are shown or hidden.
Use a loop and isRowHiddenByFilter(rowPosition) to reap out all the filtered values. You could use Array.prototype.push to add the values to a new array or use Array.prototype.splice to modify the array holdin the values returned by getValues()
Related
How to use in Google Sheets setValue only for range of filtered rows (getRange for not hidden cells)?
I managemed to solve the problem.
This script takes a google spreadsheet with 2 sheets,one with Data and one with a combination EmailAdresses.
It sends a filtered list (filter column F) of sheet Data to the corresponding salon (location) in sheet Emailadresses (var mode email). Additionally, it has the option to "store" the pdf's in your google drive (var mode store)
*/
function construct() {
// settings:
//var mode = "store";
var mode = "email";
// get list of all salons and email
var salonList = SpreadsheetApp.getActive().getSheetByName('EmailAdressen');
// set endvar for loop
var endRow = salonList.getLastRow();
// loop trough the rows to get the Salon name and the corresponding email
for(i=1;i<=endRow;i++){
var salonName = salonList.getRange(i,2).getValue();
var email = salonList.getRange(i,1).getValue();
// create an array with all salons that should be hidden (we cant pick which one to show, so we have to go the other way around...)
var filterArray = [];
// create array with all salons to hide
for(c=1;c<=endRow;c++){
// get value from email list, check if it is not the current selected one and if so add it to the list to filter out
salonFilterName = salonList.getRange(c,2).getValue();
if(salonFilterName != salonName) {
filterArray.push(salonFilterName);
}
} // end for c
// filter the list with the array we just created
var spreadsheet = filterList(filterArray);
if(mode == "email"){
// export to PDF
var pdf = exportToPdf(spreadsheet);
// email to email address belonging to this salon
emailToAddress(email, pdf);
} // end if
if(mode == "store"){
StorePdf(spreadsheet, salonName);
}
} // end for i
return;
}
function filterList(salonNameArray) {
// select data sheet
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data');
// first remove all existing filters to make sure we are on a clean sheet
if(spreadsheet.getFilter()){
spreadsheet.getFilter().remove();
}
// create the filter
spreadsheet.getRange('F:F').createFilter();
// set criteria for filter with array passed from construct
var criteria = SpreadsheetApp.newFilterCriteria().setHiddenValues(salonNameArray).build();
// apply filter
spreadsheet.getFilter().setColumnFilterCriteria(6, criteria);
return spreadsheet;
}
function exportToPdf(ss) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data');
var blob = DriveApp.getFileById(ss.getId()).getAs("application/pdf");
blob.setName(ss.getName() + ".pdf");
return blob;
}
function StorePdf(ss, salonName) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data');
var blob = DriveApp.getFileById(ss.getId()).getBlob();
blob.setName(salonName + "_" + Utilities.formatDate(new Date(), "GMT+1", "ddMMyyyy")+".pdf");
DriveApp.createFile(blob);
return;
}
function emailToAddress(email, pdf) {
MailApp.sendEmail(email, 'Type here the subject', 'Type here the body',{
attachments:[pdf]});
return;
}

Set the sourceRange of Data Validation to an array of values

I'm creating a social media outreach tracker. I want to create a drop-down list of the contact name. The problem is that I have two sources of names on two different sheets.
I wrote a script that pulls the names from the two different sources and combines them to a single array.
I was hoping to set the source range as that array.
Here is my code:
function setDataValid_(range, sourceRange) {
var rule = SpreadsheetApp.newDataValidation()
.requireValueInRange(sourceRange, true)
.build();
range.setDataValidation(rule);
}
function onEdit() {
var auditionsSheet = SpreadsheetApp.getActiveSpreadsheet();
var castingDirectorsTab = auditionsSheet.getSheetByName("Casting Directors");
var contactsTab = auditionsSheet.getSheetByName("Contacts");
var socialMediaOutreachTab = auditionsSheet.getSheetByName("Social Media Outreach");
var lastRowCD = castingDirectorsTab.getLastRow();
var lastRowContacts = contactsTab.getLastRow();
var activeCell = socialMediaOutreachTab.getActiveCell();
var activeColumn = activeCell.getColumn();
// get data
var castingDirectorNameData = castingDirectorsTab.getRange(2, 1, lastRowCD, 1).getValues();
var contactNameData = contactsTab.getRange(2, 1, lastRowContacts, 1).getValues();
//get name data to a single arrays
var castingDirectorName = [];
castingDirectorNameData.forEach(function(yr) {
castingDirectorName.push(yr[0]);
});
var contactName = [];
contactNameData.forEach(function(yr) {
contactName.push(yr[0]);
});
// get rid of the empty bits in the arrays
for (var x = castingDirectorName.length-1; x > 0; x--) {
if ( castingDirectorName[x][0] === undefined ) {
castingDirectorName.splice( x, 1 )
}
}
for (var x = contactName.length-1; x > 0; x--) {
if ( contactName[x][0] === undefined ) {
contactName.splice( x, 1 )
}
}
//combine two data sources for data validation
var combinedNames = [];
combinedNames.push(castingDirectorName + contactName);
Logger.log (combinedNames);
Logger.log( typeof combinedNames);
// data validation set up and build
if (activeColumn == 1 && auditionsSheet.getName() == "Social Media Outreach") {
var range = auditionsSheet.getRange(activeCell.getRow(), activeColumn +1);
var sourceRange = combinedNames;
setDataValid_(range, sourceRange)
}
}
When I enter a date in Col A on Social Media Outreach, nothing happens in Col 2.
I was using an existing working nested data validation script I have but the sourceRange pulls from a sheet based on the value in the active cell. Here is that code:
function setDataValid_(range, sourceRange) {
var rule = SpreadsheetApp.newDataValidation()
.requireValueInRange(sourceRange, true)
.build();
range.setDataValidation(rule);
}
function onEdit() {
var aSheet = SpreadsheetApp.getActiveSheet();
var aCell = aSheet.getActiveCell();
var aColumn = aCell.getColumn();
// data validation for Auditions Tab Projet Type to Project Details
if (aColumn == 9 && aSheet.getName() == 'Auditions') {
var range = aSheet.getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName('RefTables!' + aCell.getValue())
setDataValid_(range, sourceRange)
}
}
For this script when I select from the data validation drop-down, a new data validation comes up in the next col with the appropriate secondary data validation.
So the question is, can the source range be set to an array or do I need to put the names back into my sheet to reference a la the second script.
I've looked through the documentation and searched and can't find an answer. I'm relatively new to GAS and am not sure of all the inner workings of the data validation builder.

Auto sorting 2 sheets based on 1 of them

I'm running a spreadsheet which contains multiple sheets, in Sheet3 I'm inputting some data and running an auto sorting code, which sorts it ascending by column D.
Sheet3 Example | Sheet1 Example
The "name" and "location" in Sheet1 are imported from Sheet3 so they swap position when Sheet3 does the sorting, however, the problem is that the info from D to F (Sheet1) isn't swapping and it will display for wrong people.
This is the script I'm using:
Modified it slightly to work for a specific sheet, since I didn't need to auto sort the whole document at the time.
/*
* #author Mike Branski (#mikebranski)
* #link https://gist.github.com/mikebranski/285b60aa5ec3da8638e5
*/
var SORT_COLUMN_INDEX = 4;
var ASCENDING = true;
var NUMBER_OF_HEADER_ROWS = 2;
var SHEET_NAME = 'Sheet3';
var activeSheet;
function autoSort(sheet) {
var s = SpreadsheetApp.getActiveSheet();
if (s.getName() == SHEET_NAME) {
var range = sheet.getDataRange();
if (NUMBER_OF_HEADER_ROWS > 0) {
range = range.offset(NUMBER_OF_HEADER_ROWS, 0, (range.getNumRows() - NUMBER_OF_HEADER_ROWS));
}
range.sort( {
column: SORT_COLUMN_INDEX,
ascending: ASCENDING
} );
}
}
function onEdit(event) {
var s = SpreadsheetApp.getActiveSheet();
if (s.getName() == SHEET_NAME) {
var editedCell;
activeSheet = SpreadsheetApp.getActiveSheet();
editedCell = activeSheet.getActiveCell();
if (editedCell.getColumn() == SORT_COLUMN_INDEX) {
autoSort(activeSheet);
}
}
}
function onOpen(event) {
var s = SpreadsheetApp.getActiveSheet();
if (s.getName() == SHEET_NAME) {
activeSheet = SpreadsheetApp.getActiveSheet();
autoSort(activeSheet);
}
}
function onInstall(event) {
onOpen(event);
}
So basically when I edit Sheet3 and it does the auto sorting, I want the rows from D to F in Sheet1 to carry along with repositioning that comes from Sheet3. I hope I did manage to explain properly what I want.
I've tried without success to make it work; I can't figure out the proper way of doing this, especially due to the fact that Sheet1 table has different range.
I figured out how to fix the issue so I'll post the code here. Basically whenever you edit the column that you choose to sort by in Sheet3 (master sheet) it will first copy in the Sheet1 (target sheet) what changes you've made in A & B columns and then it will sort both sheets at the same time, this way the data from following columns in Sheet1 will carry along.
I used A & B columns in this example, since that's what I commented above, but can be different ranges as long as they're similar in size.
// Master Sheet Settings (Copy ranges must be similar in size)
var msName = 'Master Sheet';
var msSortCol = 4; // which column to trigger the sorting when you edit
var msSkipRows = 6; // how many rows to skip, if you have header rows
var msCopyRange = 'A7:B51'; // the range you want to copy
// Target Sheet Settings
var tsSortCol = 3;
var tsSkipRows = 10;
var tsName = 'Target Sheet';
var tsCopyRange = 'A11:B55';
var sortAscending = true;
var activeSheet;
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var editedCell = ss.getActiveRange().getColumnIndex();
if (ss.getSheetName() == msName) {
activeSheet = SpreadsheetApp.getActiveSheet();
if (editedCell == msSortCol) {
copyRow();
autoSort(activeSheet);
}
}
}
function copyRow() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(msName);
var values = sheet.getRange(msCopyRange).getValues();
ss.getSheetByName(tsName).getRange(tsCopyRange).setValues(values);
SpreadsheetApp.flush();
}
function autoSort() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var msheet = ss.getSheetByName(msName);
var tsheet = ss.getSheetByName(tsName);
var mrange = msheet.getDataRange();
var trange = tsheet.getDataRange();
if (ss.getSheetName() == msName) {
if (msSkipRows > 0) {
mrange = mrange.offset(msSkipRows, 0, (mrange.getNumRows() - msSkipRows));
}
if (tsSkipRows > 0) {
trange = trange.offset(tsSkipRows, 0, (trange.getNumRows() - tsSkipRows));
}
mrange.sort({ column: msSortCol, ascending: sortAscending });
trange.sort({ column: tsSortCol, ascending: sortAscending });
}
}

Resources