google sheets script to remove rows based on dates in multiple cells - google-apps-script

I am working on an app to remove old content from a spreadsheet. I have found several posts that hide or delete sheets based on specific data, but it is always in a single cell. I need something that checks dates in three cells per row.
The sheet is populated by a form that asks users to enter three dates they want a bulletin to run in our video and print bulletin at the HS where I teach. I just want to remove old data, and found countless (well, maybe 6 or 8) examples of scripts that did something similar, so I grabbed one and tried to expand the logic statement to check columns C, D, and E (using index, of course).
It works, but removes any row with a date that is not today rather than dates in all three columns. I also need to have it count cells that are empty as qualifying as old.
So I tried the script suggested below by Cooper, which is a big advancement. It is still skipping rows with empty values, and in one case removes a row that I don't want to remove
In the image shown a form accepts input from teachers who submit bulletin content for a maximum of three dates. I've formatted the sheet to show old dates in red, today in green, and future dates in yellow. I want to delete rows that have all red or empty cells. A bandaid, however, would be to force them to pick a date for all three rather than allow non entries. But it would be nice to have the code recognize blank cells as qualifying.
[![Data Sheet for daily bulletin][1]][1]
Here's what eventually worked. I'm sure there are some smoother ways of doing this, but I actually grew a bit in my programming ability by working things out through the log first, then moving on once I had evidence that what I was doing worked.
/*
Here is the thinking:
I need to have a script that is run from Add-ons that checks each cell of a range
for dates. It can be done through conditional formatting bg colors, but that is
redundant.
The first thing is to create the functions that find and return row numbers that have all
old dates. In the initial build these can be returned to the log (Logger.log()) inside
of a for(loop).
'*/
function readRows() {
var sh=SpreadsheetApp.getActiveSheet();
var rg=sh.getDataRange();
var vA=rg.getValues();
var rowsDeleted=0;
var today=new Date().valueOf();
//cycle through rows. In each row check column values for date. if date is old, add 1 to oldCount
for(var i=vA.length-1;i>=0;i--) {
var oldCount=0;
if(new Date(vA[i][2]).valueOf()<today) {
oldCount++;
}
if(new Date(vA[i][3]).valueOf()<today) {
oldCount++;
}
if(new Date(vA[i][4]).valueOf()<today) {
oldCount++;
}
if(oldCount>2) {
sh.deleteRow(i+1);
rowsDeleted++;
}
}
Logger.log(rowsDeleted);
}
Next I want to figure out how to make something that will output the daily bulletin to a Doc or pdf.

Try this:
function readRows() {
var sh=SpreadsheetApp.getActiveSheet();
var rg=sh.getDataRange();
var vA=rg.getValues();
var rowsDeleted=0;
var today=new Date().valueOf();
for(var i=vA.length-1;i>=0;i--) {
if ((new Date(vA[i][2]).valueOf()<today)||(new Date(vA[i][3]).valueOf()<today)||(new Date(vA[i][4]).valueOf()<today) ){
sh.deleteRow(i+1);
rowsDeleted++;
}
}
Logger.log(rowsDeleted);
}
I'm not completely sure what you want but this might help. When your deleting rows you should start from the bottom.

Related

Paste formula as value on a certain date

I've been digging around on line for a couple of hours now to no avail. I'm trying to write a formula(which I assume is only possible through AppScript), that based on a certain date, will re-paste contents in a cell as values only.
The issue I've had with other scripts is that in my situation I have several different values that need to be checked. I've set up a continuously updating calendar that pulls from an array with listed dates for each item. However, in that array, the row gets moved into an archive once it hits todays date. Thus, they get deleted from the calendar too.
If there is another solution to this, like pasting based off conditional formatting(this would then keep the formulas that haven't found a value yet, and paste ones that have been found as values only), that would be great too.
Any help or advice would be greatly appreciated. If you need any other info, please ask.
EDIT:
The spreadsheet link is in comments. Attached are a before and after picture to help visualize whats going on here. Since the calendar is pulling from the active sheet, and rows on the active sheet go to "archive" when J2-Today() is -1(yesterday), those then disappear from the calendar. I've tried to make a mixed sheet with both archive and active, but I get duplicates as a frequent issue.
See code below:
function archiveRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var mainSheet = ss.getSheetByName("Sheet1");
var archiveSheet = ss.getSheetByName("Sheet2");
// last row should be on the data itself, same with column
var lastRow = mainSheet.getLastRow();
var lastCol = mainSheet.getLastColumn();
// set time to 0 so we can only compare dates
var dateToday = new Date().setHours(0, 0, 0, 0);
var range = mainSheet.getRange(1, 1, lastRow, lastCol);
var values = range.getValues();
// remove header to only process data
var header = values.shift();
var indices = [], difference = [];
// remove dates that are earlier than today
// collect rows and indices data when condition is not met
values.forEach(function (row, index) {
if (row[9] >= dateToday)
return true;
difference.push(row);
indices.push(index);
});
// remove the row with earlier date
// offset with 2 due to 0-indexing and header
indices.forEach(index => mainSheet.deleteRow(index + 2));
// all rows that were deleted are copied to archive sheet
difference.forEach(row => archiveSheet.appendRow(row));
}
Sample Data:
Output:
Note:
If you need to have this run on a daily basis, Time Driven trigger is your friend. Just have it set on a daily basis triggering archiveRows
Make sure that the main sheet only contains the data. (I see your sheet has rows containing 9AM values below. Make sure to clear those as the script will mistakenly include it on the processing)
Adjust date condition if needed.

Is there a way to display values different based on date?

I'm trying to grab data vertically and display it horizontally with a column in between each value.
Here is example data:
Here is what I'm trying to get it to do:
I've tried exhausting everything I currently know to figure this out but I can't seem to get it and I believe it's an easy problem to fix. I'm just not having much luck with my keywords in google.
ps: just noticed I had some columns hidden still. In this case lets pretend the columns in examples are A/B/C/D/E.
Summarizing identical dates on multiple rows into separate columns on the same row
I don't completely understand how you went from example 1 to example 2 but it is clear that you want to summarize multiple rows with the same date into multiple columns on one row and that's what this function is an example of.
I'm guessing that once you provide a better explanation of what your trying to accomplish and how it's supposed to work then you may get some answers that are a lot easier to figure out.
Anyway I offer this solution as a possibility that you can start with if you like. I'm using a key/value object to keep track of the number of identical dates and if the method dObj.hasOwnProperty() returns false then it also helps to identify new dates.
The Code:
function condenseDates(){
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('sheet name');//you need to change this for the appropriate sheet name
var rg=sh.getDataRange();//get all data values including headers
var vA=rg.getValues();
var dObj={};//data object which is used to identify new new dates and keep track of the run columns for identical dates.
var d=0;//delete counter
for(var i=1;i<vA.length;i++){
if(dObj.hasOwnProperty(vA[i][0])) {//if it does not have this property then it is a new date
dObj[vA[i][0]]=Number(dObj[vA[i][0]])+1;//if it does have this property then increment the DObj value which keeps track of the run column it will go into
var rg=sh.getRange(1,2 + Number(dObj[vA[i][0]]));//this is the header column cell for this dObj column
if(rg.isBlank()){//if it is blank then write a new one
rg.setValue('Run' + dObj[vA[i][0]]);
}
sh.getRange(Number(dObj.currentRow),2 + Number(dObj[vA[i][0]])).setValue(vA[i][1]);//put value for this line in the appropriate column in the currentRow
sh.deleteRow(i-d+1);//delete this line
d++;//increment the delete counter
}else{//it is a new date
dObj[vA[i][0]]=1;
dObj['currentRow']=i-d+1;//Current Data Row
var rg=sh.getRange(1,3);
if(rg.isBlank()){//if header has no label in the first run column then write it
rg.setValue('Run' + 1);
}
sh.getRange(Number(dObj.currentRow),2 + Number(dObj[vA[i][0]])).setValue(vA[i][1]);//write data in the appropriate column in this case it is always column3
}
}
sh.deleteColumn(2);
}
The Example Spreadsheet before running the script:
The Example Spreadsheet after running the script:
The value in column 2 for each consecutive identical date is placed in the next column on the first row in which that date appears. At the end column2 is deleted.
Javascript Object Reference
Google Apps Script Documentation
SpreadsheetApp Documentation
I played around with this and this version doesn't required additional identical dates to be consecutive. It create a unique currentRow for each date and that current row is used for all remaining identical dates even if they occur after other remaining dates. ie the dates don't have to be sorted.
function condenseDates(){
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('36');//you need to change this for the appropriate sheet name
var rg=sh.getDataRange();//get all data values including headers
var vA=rg.getValues();
var dObj={};//data object which is used to identify new new dates and keep track of the run columns for identical dates.
var d=0;//delete counter
for(var i=1;i<vA.length;i++){
if(dObj.hasOwnProperty(vA[i][0])) {//if it does not have this property then it is a new date
dObj[vA[i][0]]=Number(dObj[vA[i][0]])+1;//if it does have this property then increment the DObj value which keeps track of the run column it will go into
var rg=sh.getRange(1,2 + Number(dObj[vA[i][0]]));//this is the header column cell for this dObj column
if(rg.isBlank()){//if it is blank then write a new one
rg.setValue('Run' + dObj[vA[i][0]]);
}
sh.getRange(Number(dObj[vA[i][0].toString()+'currentRow']),2 + Number(dObj[vA[i][0]])).setValue(vA[i][1]);//put value for this line in the appropriate column in the currentRow
sh.deleteRow(i-d+1);//delete this line
d++;//increment the delete counter
}else{//it is a new date
dObj[vA[i][0]]=1;
dObj[vA[i][0].toString()+'currentRow']=i-d+1;//A unique Current Data Row for each date when additional identical dates will expand into additional columns.
var rg=sh.getRange(1,3);
if(rg.isBlank()){//if header has no label in the first run column then write it
rg.setValue('Run' + 1);
}
sh.getRange(Number(dObj[vA[i][0].toString()+'currentRow']),2 + Number(dObj[vA[i][0]])).setValue(vA[i][1]);//write data in the appropriate column in this case it is always column3
}
}
sh.deleteColumn(2);
}

Auto fill a column with formulas when new rows are added

I have an automation workflow (Zapier) which adds a new row to a Google Sheets whenever a new card is added in Trello. We use it to track statistics for departments etc. I have some extra fields which transform a date field into things like weeknumber, day, month, year... simple stuff.
I need to write a script which looks out for new rows entered into the spreadsheet and then auto-fills the other columns with preset formulas.
Example layout:
Columns populated via the automation:
a,b,d,e
Columns to populate via the script.
f,g,h,i,j,k
I've seen a few scripts that are similar but don't take a formula from the row above as the content to autofill.
Note: The formulas for each column are available in the row above which has been added manually by me (for now). So, in theory, the script could reference the cell above for the correct formula to use.
Note 2: I cannot use the ARRAYFORMULA method because Zapier will see the row as having content and will skip to the next empty row.
I think this will do it.
function fillInFGHIJK()
{
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var rg=sh.getDataRange();
var vA=rg.getValues();
for(var i=0;i<vA.length;i++)
{
if(!vA[i][5] && !vA[i][6] && !vA[i][7] && !vA[i][8] && !vA[i][9] && !vA[i][10])
{
sh.getRange(i,6,1,6).copyTo(sh.getRange(i+1,6,1,6));//According to Michelle this will works better because the cell references change appropriately
}
}
}

Google Sheets OnEdit script: copy value from specific columns

We are using Google sheets for tracking attendance. Previously, the teachers were entering P, T, or A (for present, tardy, absent) for each period. I would still like users to have the option to enter a value for each period in a week, however it would be a great time saver if they could enter one value for the whole day.
What I'd like is that if a value is entered into any one of the "0" periods (green columns) with a "P" or "A" (data validation limits those options) an OnEdit function would copy that same letter ("P" or "A") to the following 8 columns and then delete the original value. (without the deletion the totals on the far right columns will be off). I would not want the OnEdit to be activitated based on edits in any of the non-green columns.
I will eventually have several tabs, each one a different week, but each exactly the same... so I'm thinking the function should work within whatever the activesheet is.
https://docs.google.com/spreadsheets/d/1NKIdNY4k66r0zhJeFv8jYYoIwuTq0tCWlWin5GO_YtM/edit?usp=sharing
Thank you for your help,
I wrote some code to get you started with your project. (I am also a teacher) You will have to make some changes based on what you are going for and it can probably be optimised to run faster. Good luck!
function onEdit(e) {
//create an array of the columns that will be affected
var allColumns = [2, 10];
//get the number values of the column and row
var col = e.range.getColumn();
var row = e.range.getRow();
//get the A1 notation of the editted cell for clearing it out
var cell = e.range.getA1Notation();
//only run if the cell is in a column in the allColumns array
if(allColumns.indexOf(col) > -1) {
//run the for loop for the next 8 cells
for(var i = col + 1; i < col + 9; i++) {
SpreadsheetApp.getActiveSheet().getRange(row, i).setValue(e.value);
SpreadsheetApp.getActiveSheet().getRange(cell).setValue('');
}
}
}

Sorting duplicate submissions by date

I have a Google form that deposits data to a google spreadsheet. All the data from the first form filled out will be deposited on row 1, then the next form filled out will be deposited on row 2, etc. A problem that I have been having is that sometimes people lose their link to edit their submitted form and they submit a whole new entry creating a duplicate row on google sheets with updated information.
I am trying to make sheet2 that will filter out the duplicate entries but still keep the order that they first appeared. I have considered using UNIQUE(range) function however the problem is that the unique function takes the first time the name appears. What I want it to do is keep the position that the first name appears in but take the data of the latest time the name appears because that will have the updated information. I cannot figure out a way to do this on my own. Is there anyone who is more knowledgeable that can help?
Below is an example of what I am hoping to achieve. The first time John appears is overwritten by the latest time it appears.
I think this should do it for you.
function findCopyDeleteDupes()
{
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sht=ss.getActiveSheet();
var rng=sht.getDataRange();
var rngA=rng.getValues();
var nodupes=[];
var dupeRows=[];
for(var i=0;i<rngA.length;i++)
{
var idx=nodupes.indexOf(rngA[i][1]);
if(idx==-1)//if not in nodupes then put it in nodupes
{
nodupes.push(rngA[i][1]);
}
else
{
for(var j=0;j<rngA[i].length;j++)
{
rngA[idx][j]=rngA[i][j];//Copy data from the latest time to the first position
}
dupeRows.push(i+1);//add this row to the delete rows array
}
}
rng.setValues(rngA);
for(var i=dupeRows.length-1;i>-1;i--)//delete rows from the bottom first
{
sht.deleteRow(dupeRows[i])
}
}