Conditional combine of array objects Google apps script - google-apps-script

I have a two dimensional array which elements needs to be combined if two of the elements of the sub arrays are equal.
Example:
I have this data:
Name
Date start
Date end
First
startDate1
endDate1
Second
startDate2
endDate2
Third
startDate1
endDate2
Fourth
startDate1
endDate1
Fifth
startDate1
endDate1
Sixth
startDate3
endDate2
I need this data:
Name
Date start
Date end
First, Fourth, Fifth
startDate1
endDate1
Second
startDate2
endDate2
Third
startDate1
endDate1
Sixth
startDate3
endDate2
The data is represented this way for example:
var event1 = [name, startDate1, endDate1];
var event2 = [name, startDate2, endDate2];
var event3 = [name, startDate1, endDate2];
var event4 = [name, startDate1, endDate1];
var event5 = [name, startDate1, endDate1];
var event6 = [name, startDate3, endDate2];
var allEvents = [event1, event2, event3, event4, event5, event6 ]
And I need to combine the elements with concatenated names if both start and end dates are equal.
Any help is much appreciated.

I'll leave it up to you where to put the results but try this:
function test() {
try {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName("Test");
var unique = [];
var data = sh.getRange(1,1,sh.getLastRow(),sh.getLastColumn()).getValues();
unique.push(data[1]);
var append = true;
for( var i=2; i<data.length; i++ ) {
append = true;
for( var j=0; j<unique.length; j++ ) {
if( ( data[i][1] === unique[j][1] ) && ( data[i][2] === unique[j][2] ) ) {
unique[j][0] = unique[j][0]+", "+data[i][0];
append = false;
break;
}
}
if( append ) unique.push(data[i]);
}
console.log(unique);
}
catch(err) {
console.log(err);
}
}
consol.log:
8:15:15 AM Info [ [ 'First, Fourth, Fifth', 'startDate1', 'endDate1' ],
[ 'Second', 'startDate2', 'endDate2' ],
[ 'Third', 'startDate1', 'endDate2' ],
[ 'Sixth', 'startDate3', 'endDate2' ] ]

Related

Why is this function not deleting many rows at once correctly?

This function works with a little bit of data, but not with hundreds of rows and I wonder if I'm missing some Spreadsheet.flush() or something of this nature.
const values = [["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"]];
function DeleteRows(sheetName, year) {
sheetName = 'Saved Budgets'//For tests
year = '2022' //For tests
var SS = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName); //Get Open Lines Sheet
var lastRow = SS.getLastRow();
var range = SS.getRange(2, 1, lastRow - 1, 1); //get range
range.sort({ column: 1, ascending: false }) // filter data descending
var firstRowToDelete = 0;
var numOfRows = 1; // starting row to be increment and become the number of rows
var values = range.getValues();//Got it for comparison
for (let a = 0; a < values.length; a++) {
let dt = new Date(values[a]).getFullYear();
if (dt == year) {
firstRowToDelete = parseInt(a);
numOfRows++
}
}
if (numOfRows != 1) {
numOfRows = numOfRows - 1 // minus 1 to get the last row
SS.deleteRows(firstRowToDelete, numOfRows);
}
range.sort({ column: 1, ascending: true }) // filter data again ascending
}
If you want to delete rows that the column "A" is year = '2022', firstRowToDelete = parseInt(a); is the last index of the rows that the column "A" is year = '2022'. And, numOfRows is the number of rows. In this case, I'm worried that all rows that the column "A" is year = '2022' cannot be deleted. And also, when the values are large, the rows for deleting might be over the bottom of the sheet, and/or range of range.sort({ column: 1, ascending: true }) might be over the bottom of the sheet. I thought that this might be the reason for your issue.
If you want to remove this issue, when your script is modified, how about the following modification?
From:
for (let a = 0; a < values.length; a++) {
let dt = new Date(values[a]).getFullYear();
if (dt == year) {
firstRowToDelete = parseInt(a);
numOfRows++
}
}
if (numOfRows != 1) {
numOfRows = numOfRows - 1 // minus 1 to get the last row
SS.deleteRows(firstRowToDelete, numOfRows);
}
range.sort({ column: 1, ascending: true }) // filter data again ascending
To:
for (let a = 0; a < values.length; a++) {
let dt = new Date(values[a]).getFullYear();
if (dt == year) {
if (firstRowToDelete == 0) firstRowToDelete = a + 2; // Modified
numOfRows++
}
}
if (numOfRows != 1) {
numOfRows = numOfRows - 1;
SS.deleteRows(firstRowToDelete, numOfRows);
}
SS.getRange(2, 1, SS.getLastRow() - 1, 1).sort({ column: 1, ascending: true }); // Modified
As another modification, how about the following modification?
From:
var values = range.getValues();//Got it for comparison
for (let a = 0; a < values.length; a++) {
let dt = new Date(values[a]).getFullYear();
if (dt == year) {
firstRowToDelete = parseInt(a);
numOfRows++
}
}
if (numOfRows != 1) {
numOfRows = numOfRows - 1 // minus 1 to get the last row
SS.deleteRows(firstRowToDelete, numOfRows);
}
range.sort({ column: 1, ascending: true }) // filter data again ascending
To:
var values = range.getDisplayValues();
var numOfRows = values.filter(([a]) => new Date(a).getFullYear() == year).length;
if (numOfRows > 0) {
var firstRowToDelete = values.findIndex(([a]) => new Date(a).getFullYear() == year);
SS.deleteRows(firstRowToDelete > -1 ? firstRowToDelete + 2 : firstRowToDelete, numOfRows);
}
SS.getRange(2, 1, SS.getLastRow() - 1, 1).sort({ column: 1, ascending: true });
Try this:
NOTE: This is all based on the assumption that the values global variable is actually a data in the spreadsheet, and you would want to remove all data with 2022.
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); //you can change this to specify a specific sheet
var range = ss.getRange(2,1,ss.getLastRow(), ss.getLastColumn());
var values = range.getValues();
var year = /2022/; // change this to filter other years
var newval = values.filter(x=>year.test(x) ? null : x);
console.log(newval); //to check if it populates the correct data during logging.
range.clearContent(); //clears the data based on the current range keeping the formatting.
var newrange = ss.getRange(2,1,newval.length, ss.getLastColumn()); //creates a new range based on the size of `newval`
newrange.setValues(newval);
}
Explanation:
var range = ss.getRange(2,1,ss.getLastRow(), ss.getLastColumn()); gets the current data on the spreadsheet, including the columns.
Using var values = range.getValues(); we get a 2D array structure of the data on the spreadsheet.
Using filter() and test() method on var newval = values.filter(x=>year.test(x) ? null : x); using a ternary operator to test whether an array element contains the year to filter out.
range.clearContent(); to delete the contents of the range.
var newrange = ss.getRange(2,1,newval.length, ss.getLastColumn()); creates a new range based on the new array.
newrange.setValues(newval); sets the new value on the spreadsheet
Screenshots:
NOTE: Multiple columns in the data are for testing to see dynamic deletion even if there is additional data on the columns.
Initial data:
After running the script:
Execution duration:
References:
https://developers.google.com/apps-script/reference/spreadsheet/range#clearContent()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test

How to sorting within same date data based on time in google sheet using Apps Script?

I want to sort data based on date(Column A) and start time(Column I). If date column have more than 1 same date then again shorting by start time within this same date contain rows.
i have a code already that sorting by date.
function autoSort() {
var headerRows = 1;
var sortFirst = 1; // 1 is Column "A"
var sortFirstAsc = false; // When it's "true", the order is ascending.
var sortSecond = 9; // 3 is Column "C"
var sortSecondAsc = false; // When it's "true", the order is ['OPEN','YES','NO'].
// Retrieve values from Spreadsheet.
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.getLastRow()-headerRows, sheet.getLastColumn());
var values = range.getValues();
// Sort the date of column "A".
var s1 = sortFirstAsc ? 1 : -1;
values.sort(function(a, b) {return (a[sortFirst - 1] < b[sortFirst - 1] ? -s1 : s1)});
// Sort the values of column "C" with the custom sort using the keys.
var sortOrder = ['OPEN','YES','NO'];
var s2 = sortSecondAsc ? 1 : -1;
values.sort(function(a, b) {
var i1 = sortOrder.indexOf(a[sortSecond - 1]);
var i2 = sortOrder.indexOf(b[sortSecond - 1]);
var vlen = values.length;
return s2 * ((i1 > -1 ? i1 : vlen) - (i2 > -1 ? i2 : vlen));
});
sheet.getRange(2, 1, values.length, values[0].length).setValues(values);
}

Confirm that a value is in a spreadsheet column using Google Apps Script

This function sends email reminders based on a few conditions. One of the things I need to check for is that the Visit ID (which is in column 11 in the "email log" sheet) exists in a separate sheet ("DATA", stored in the enrollmentData variable). How do I search this array and return the ID to match in the IF statement below?
function sendReminders() {
var ss = SpreadsheetApp.getActiveSpreadsheet ();
var sheet = ss.getSheetByName("Email Log");
var rows = sheet.getLastRow()-1;
var startRow = 2; // First row of data to process
var numRows = rows; // Number of rows to process
var now = new Date();
// Fetch the range of cells
var dataRange = sheet.getRange(startRow, 1, numRows, 22)
// Fetch values for each row in the Range.
var data = dataRange.getValues();
var today = Utilities.formatDate(new Date(), "GMT-5", "m/d/yyyy")
var reminderSent = "Reminder Sent";
//get email body and subject
var bodyTem = ss.getSheetByName("Email Templates").getRange('b8').getValues();
var subject = ss.getSheetByName("Email Templates").getRange('d13').getValues();
//get enrollments data to search for visit ID
var enrollmentData = ss.getSheetByName("DATA").getRange('H:H').getValues();
for (var i = 0; i < data.length; i++) {
var row = data[i];
//set conditions
var sendReminder = row[18];
var reminderDate = row[19];
var reminderStatus = row[20];
var visitID = row[11]
//need condition to look for visit ID to not include already cancelled. Search enrollmentData for visitID and return as foundID for conditional below
if (sendReminder == "Yes" && reminderStatus != reminderSent && reminderDate >= today && visitID == foundID) {
//assemble email
var studentEmail = row[13];
var firstName = row[12];
var instructor = row[0];
var body1 = bodyTem.replace(/*name*/gi,firstName);
var body2 = body1.replace(/*instructorFull*/gi,instructor);
MailApp.sendEmail(studentEmail, subject, body2);
//need to write in that the reminder email was sent.
sheet.getRange(startRow + i, 20).setValue(reminderSent);
sheet.getRange(startRow + i, 21).setValue(now);
};
};
};
You want to search the array
var enrollmentData = ss.getSheetByName("DATA").getRange('H:H').getValues();
The method getValues always returns a double array: in this case, it's of the form [[1], [2], [3],..] since each row has one element. I usually flatten this:
var enrollmentDataFlat = enrollmentData.map(function(row) {return row[0];});
Now enrollmentDataFlat is like [1, 2, 3, ..] so indexOf will work as usual:
if (enrollmentDataFlat.indexOf(visitID) != -1) {
// it's there
}
else {
// it's not there
}

Google script to remove duplicate rows in spreadsheet and keep the most recent entry based on timestamp

I have a google spreadsheet that is populated by a form, so timestamps are automatically added in the first column for each row. I have a script that removes duplicate rows in my spreadsheet (5 specific columns must be the same for it to be a duplicate, while some other columns are ignored), but I want to modify it so that if I have multiple rows for the same person's data but with different timestamps, the script will keep the most recent row. How would I do this? Thanks!
/** removes duplicate rows in studentsheet **/
function removeDuplicates() {
var newData = new Array();
for(i in studentdata){
var row = studentdata[i];
var duplicate = false;
for(j in newData){
if(row[1] == newData[j][1] && row[2] == newData[j][2] && row[5] == newData[j][5] && row[9] == newData[j][9] && row[10] == newData[j][10]){
duplicate = true; //first name, last name, grade, dad's first name, and mom's first name are the same
}
}
if(!duplicate){
newData.push(row);
}
}
StudentSheet.clearContents();
StudentSheet.getRange(1, 1, newData.length, newData[0].length).setValues(newData);
sortSheet(); //sorts sheet by 2 columns
}
Here's a different approach, concattenating all columns in a single string, to save it as a object for faster searching, if you have a big sheet this can help:
function deleteDuplicateRowsSaveRecent(){
var verifiedRows = {},
curretnRow = "",
usedRows = [1, 2, 5, 9, 10];
for( lin in studentdata){
curretnRow = "";
for( ind in usedRows )
curretnRow += studentdata[ lin ][ usedRows[ ind ] ];
if(verifiedRows[ curretnRow ]){
if( studentdata[ lin ][ dateColumn ] > studentdata[ verifiedRows[ curretnRow ] ][ dateColumn ] ){
studentSheet.deleteRow(verifiedRows[ curretnRow ])
verifiedRows[ curretnRow ] = lin;
}else
studentSheet.deleteRow( lin );
}
else
verifiedRows[ curretnRow ] = lin;
}
}
Not tested but hopefully you'll get the logic.
Sorts data so grouped by 'test for duplicates' data and then by date descending within group,
Starts at bottom making bottom row current row.
Current row 'test for duplicates' tested against 'test for duplicates' in row above.
If current row duplicate of one above then deletes current row leaving the row above with the later date.
If not duplicate the row above becomes the current row and tested against the one above that deleting the current row if duplicate and moving on if not.
When complete replaces existing data in spreadsheet with modified data properly sorted.
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName("Form Responses 1");
// dataRange should not include headers
var dataRange = s.getRange(2, 1, s.getLastRow() -1, s.getLastColumn())
var data = dataRange.getValues();
// Test for duplicate columns.
// numbers below = column number; A=1 B=2 etc.
var lName = 2;
var fName = 3;
var grade = 5;
var dad = 9;
var mom = 10;
for( var i = 0; i < data.length; i++ ) {
// add sortable date to beginning of rows
data[i].unshift(Utilities.formatDate(data[i][0], "GMT", "yyyyMMddHHmmss"));
// add sortable test for duplicates string in front of above date.
// Placing the below in the order to be sorted by will save
// a separate sort later
data[i].unshift(
data[i][lName].toLowerCase().trim() +
data[i][fName].toLowerCase().trim() +
data[i][grade].toString().trim() +
data[i][dad].toLowerCase().trim() +
data[i][mom].toLowerCase().trim())
}
// sort to group rows by test data
data.sort();
// reverse sort so latest date at top of each duplicate group.
data.reverse();
// test each row with one above and delete if duplicate.
var len = data.length - 1;
for( var i = len; i > 0; i-- ) {
if(data[i][0] == data[i-1][0]) {
data.splice(i, 1);
}
}
// remove temp sort items from beginning of rows
for( var i = 0; i < data.length; i++ ) {
data[i].splice(0, 2);
}
// Current sort descending. Reverse for ascending
data.reverse();
s.getRange(2, 1, s.getLastRow(), s.getLastColumn()).clearContent();
s.getRange(2, 1, data.length, data[0].length).setValues(data);
}
After working up my previous answer, which I believe to be the better, I considered another approach that would cause less disruption to your existing code.
You push the first non duplicate from studentdata to the new array so if studentdata is sorted by timestamp descending before the test the first non duplicate encountered that is pushed will be the latest.
Placing the following at the very beginning of you function should achieve
for( var i = 0; i < studentdata.length; i++ ) {
// add sortable date to beginning of rows
studentdata[i].unshift(Utilities.formatDate(studentdata[i][0], "GMT", "yyyyMMddHHmmss"));
}
studentdata.sort();
studentdata.reverse();
// remove temp sort date from beginning of rows
for( var i = 0; i < studentdata.length; i++ ) {
studentdata[i].splice(0, 1);
}
I decided to sort the date of submission column so that the most recent date was on top, and then run my original duplicate removal script. It seemed to work.
/** sorts studentsheet by most recent submission, by last name, and then by grade/role (columns) **/
function sortSheet() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Students");
sheet.sort(1, false); //sorts column A by date of submission with most recent on top
sheet.sort(3, true); // Sorts ascending (A-Z) by column C, last name
sheet.sort(6, true); // Sorts ascending (A-Z) by column F, grade/role
}
function removeDuplicates(){
var newData = new Array();
for(i in studentdata){
var row = studentdata[i];
var duplicate = false;
for(j in newData){
if(row[1] == newData[j][1] && row[2] == newData[j][2] && row[5] == newData[j][5] && row[9] == newData[j][9] && row[10] == newData[j][10]){
duplicate = true; //date of submission, first name, last name, grade, dad's first name, and mom's first name are the same
}
}
if(!duplicate){
newData.push(row);
}
}
StudentSheet.clearContents();
StudentSheet.getRange(1, 1, newData.length, newData[0].length).setValues(newData);
}

How to automatically add a timestamp in google spreadsheet

I have a sheet in my Google spreadsheet that contains 5 cells, the first 3 contains only words while the last 2 contains time, specifically a timestamp.
cell 1 = data
cell 2 = data
cell 3 = data
cell 4 = time start
cell 5 = time ended
Now, what I want is when cell 1 is supplied with data, a timestamp will automatically appear in cell 4. And when cell 2 and cell 3 is supplied with data, a timestamp will be the new value for cell 5.
My friend give me a code, that should pasted in Script editor:
function readRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
Logger.log(row);
}
};
And
function onOpen() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Read Data",
functionName : "readRows"
}];
spreadsheet.addMenu("Script Center Menu", entries);
};
function timestamp() {
return new Date()
}
and this code is pasted in =IF(B6="","",timestamp(B6))cell 4 and this one =IF(D6="","",timestamp(C6&B6)) is on cell 5. in his example tracker its working. But when i copied it to mine, the output in cell 4 and cell 5 is the Date today and not the time.
can anyone help me? why does it output the date and not the time?
You can refer this tutorial, if this helps.
In the script code, change
var timestamp_format = "MM-dd-yyyy"; // Timestamp Format.
to
var timestamp_format = "MM-dd-yyyy hh:mm:ss"; // Timestamp Format.
This should probably help you.
I just came across this problem and I modified the code provided by Internet Geeks.
Their code works by updating a specified column, the timestamp is inserted in the same row in another specified column.
What I changed is that I separated the date and the time, because the timestamp is a string, not a date format. My way is useful for generating graphs.
It works by specifying the column to track for changes, and then creating an upDate and upTime columns for the date and time respectively.
function onEdit(event) {
var timezone = "GMT+1";
var date_format = "MM/dd/yyyy";
var time_format = "hh:mm";
var updateColName = "Резултат";
var DateColName = "upDate";
var TimeColName = "upTime";
var sheet = event.source.getActiveSheet(); // All sheets
// var sheet = event.source.getSheetByName('Test'); //Name of the sheet where you want to run this script.
var actRng = event.source.getActiveRange();
var editColumn = actRng.getColumn();
var index = actRng.getRowIndex();
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues();
var dateCol = headers[0].indexOf(DateColName);
var timeCol = headers[0].indexOf(TimeColName);
var updateCol = headers[0].indexOf(updateColName);
updateCol = updateCol + 1;
if (dateCol > -1 && timeCol > -1 && index > 1 && editColumn == updateCol) {
// only timestamp if 'Last Updated' header exists, but not in the header row itself!
var cellDate = sheet.getRange(index, dateCol + 1);
var cellTime = sheet.getRange(index, timeCol + 1);
var date = Utilities.formatDate(new Date(), timezone, date_format);
var time = Utilities.formatDate(new Date(), timezone, time_format);
cellDate.setValue(date);
cellTime.setValue(time);
}
}
Hope this helps people.
Updated and simpler code
function onEdit(e) {
var sh = e.source.getActiveSheet();
var sheets = ['Sheet1']; // Which sheets to run the code.
// Columns with the data to be tracked. 1 = A, 2 = B...
var ind = [1, 2, 3].indexOf(e.range.columnStart);
// Which columns to have the timestamp, related to the data cells.
// Data in 1 (A) will have the timestamp in 4 (D)
var stampCols = [4, 5, 6]
if(sheets.indexOf(sh.getName()) == -1 || ind == -1) return;
// Insert/Update the timestamp.
var timestampCell = sh.getRange(e.range.rowStart, stampCols[ind]);
timestampCell.setValue(typeof e.value == 'object' ? null : new Date());
}
I made a slightly different version, based also on the code from Internet Geeks
In order to support multiple named sheets, and because Google Sheets Script doesn't currently support Array.prototype.includes(), I included the polyfill mentioned here
Also, in my version, the timestamp marks the date of creation of that row's cell, not the date of the last update as in the other scripts provided here.
function onEdit(event) {
var sheetNames = [
'Pounds £',
'Euros €'
]
var sheet = event.source.getActiveSheet();
if (sheetNames.includes(sheet.getName())){
var timezone = "GMT";
var dateFormat = "MM/dd/yyyy";
var updateColName = "Paid for ...";
var dateColName = "Date";
var actRng = sheet.getActiveRange();
var editColumn = actRng.getColumn();
var rowIndex = actRng.getRowIndex();
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues();
var dateCol = headers[0].indexOf(dateColName) + 1;
var updateCol = headers[0].indexOf(updateColName) + 1;
var dateCell = sheet.getRange(rowIndex, dateCol);
if (dateCol > 0 && rowIndex > 1 && editColumn == updateCol && dateCell.isBlank())
{
dateCell.setValue(Utilities.formatDate(new Date(), timezone, dateFormat));
}
}
}
// https://stackoverflow.com/a/51774307/349169
// https://tc39.github.io/ecma262/#sec-array.prototype.includes
if (!Array.prototype.includes) {
Object.defineProperty(Array.prototype, 'includes', {
value: function(searchElement, fromIndex) {
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
// 1. Let O be ? ToObject(this value).
var o = Object(this);
// 2. Let len be ? ToLength(? Get(O, "length")).
var len = o.length >>> 0;
// 3. If len is 0, return false.
if (len === 0) {
return false;
}
// 4. Let n be ? ToInteger(fromIndex).
// (If fromIndex is undefined, this step produces the value 0.)
var n = fromIndex | 0;
// 5. If n ≥ 0, then
// a. Let k be n.
// 6. Else n < 0,
// a. Let k be len + n.
// b. If k < 0, let k be 0.
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
function sameValueZero(x, y) {
return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
}
// 7. Repeat, while k < len
while (k < len) {
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
// b. If SameValueZero(searchElement, elementK) is true, return true.
if (sameValueZero(o[k], searchElement)) {
return true;
}
// c. Increase k by 1.
k++;
}
// 8. Return false
return false;
}
});
}