So in the project I want to do I have a google sheet with timestamps and names next to those timestamps in the spreadsheet. I am having trouble accounting for duplicates and giving the name multiple timestamps in another google sheet.
for(var i = 0; i < length; i++){//for loop 1
if(currentCell.isBlank()){//current cell is blank
daySheet.getRange(i+10, 2).setValue(fullName);//set name to first cell
daySheet.getRange(i+10,3).setValue(pI);
daySheet.getRange(i+10,day+3).setValue(1);
}else if(counter > 1 ){//the index of the duplicate in the sheet month
//if counter is > 1 then write duplicates
for(var t = 1; t <= sheetLength ; t++){//loop through sign in sheet
//current index i
if(signInLN == signInSheet.getRange(t+1,3).getValue()){
//if there is a match
daySheet.getRange(t+10,day+3).setValue(1);
//day is equal to the day I spliced from the timestamp
//at this point I am confused on how to get the second date that has the same
//name and add to the row with the original name.
//when i splice the timestamp based on the row of index i, with duplicates I get
//the day number from the first instance where the name is read
}
}
}//for loop 1
How can I get this to work with duplicates so I can account for the dates but make sure that if there are
any duplicates they will be added to the row of the original name
Google Sheet EX:
12/10/2020 test1
12/11/202 test2
12/15/2020 test1
Should be something like this:
name 10 11 12 13 14 15 16
test1 1 1
test2 1
//the one is to identify that the date is when the user signed in on the sheets.
Sample Spreadsheet:
Code snippet done with Apps Script, adapt it to your needs.
use Logger.log() in case you don't understand parts of code
It is done mainly with functional JavaScript
function main(){
var inputRange = "A2:B";
var sheet = SpreadsheetApp.getActive().getSheets()[0]
var input = sheet.getRange(inputRange).getValues(); //Read data into array
var minDate, maxDate;
var presentDates = input.map(function(row) {return row[0];}); //Turns input into an array of only the element 0 (the date)
minDate = presentDates.reduce(function(a,b) { return a<b?a:b}); //For each value, if its the smallest: keep; otherwise: skip;
maxDate = presentDates.reduce(function(a,b) { return a>b?a:b}); //Same as above, but largest.
var dates = [];
for (var currentDate = minDate; currentDate <= maxDate; currentDate.setDate(currentDate.getDate()+1)) {
dates.push(getFormattedDate(currentDate)); //Insert each unique date from minDate to maxDate (to leave no gaps)
}
var uniqueNames = input.map(function(row) {return row[1];}) //Turns input into an array of only the element at 1 (the names)
.filter(function (value, index, self) {return self.indexOf(value) === index;}); //Removes duplicates from the array (Remove the element if the first appearence of it on the array is not the current element's index)
var output = {}; //Temporary dictionary for easier counting later on.
for (var i=0; i< dates.length; i++) {
var dateKey = dates[i];
for (var userIndex = 0; userIndex <= uniqueNames.length; userIndex++) {
var mapKey = uniqueNames[userIndex]+dateKey; //Match each name with each date
input.map(function(row) {return row[1]+getFormattedDate(row[0])}) //Translate input into name + date (for easier filtering)
.filter(function (row) {return row === mapKey}) //Grab all instances where the same date as dateKey is present for the current name
.forEach(function(row){output[mapKey] = (output[mapKey]||0) + 1;}); //Count them.
}
}
var toInsert = []; //Array that will be outputted into sheets
var firstLine = ['names X Dates'].concat(dates); //Initialize with header (first element is hard-coded)
toInsert.push(firstLine); //Insert header line into output.
uniqueNames.forEach(function(name) {
var newLine = [name];
for (var i=0; i< dates.length; i++) { //For each name + date combination, insert the value from the output dictionary.
var currentDate = dates[i];
newLine.push(output[name+currentDate]||0);
}
toInsert.push(newLine); //Insert into the output.
});
sheet.getRange(1, 5, toInsert.length, toInsert[0].length).setValues(toInsert); //Write the output to the sheet
}
// Returns a date in the format MM/dd/YYYY
function getFormattedDate(date) {
var year = date.getFullYear();
var month = (1 + date.getMonth()).toString();
month = month.length > 1 ? month : '0' + month;
var day = date.getDate().toString();
day = day.length > 1 ? day : '0' + day;
return month + '/' + day + '/' + year;
}
Run script results:
Related
I have a sheet with 4 columns, as shown below:
1
Date
Item Name
Counter
Flag
3
Date 1
Item A
1
4
Date 1
Item B
1
5
Date 2
Item B
2
6
Date 3
Item A
2
1
7
Date 3
Item B
3
8
Date 4
Item A
1
9
Date 5
Item A
2
Currently, I'm using a countif function [=countif(B$2:B2,B2)] to count the number of times a specific item appears in the spreadsheet. However, I need to find a way to restart the counter if there is a 1 in column D. In this case, this would mean that the formula in row 8 column C would be [=COUNTIF(B$8:B8,B8)] and would continue counting until it finds another row with a 1 in column D (e.g., formula in column C row 9 would be =COUNTIF(B$8:B9,B9). It would also ideally check whether there is a prior row with a 1 in column D, not through the order of the sheet, but by checking that it's date is smaller (and yet the closest date with a 1 in column D).
I've written the following script, which sets the row with a 1 in column D to 0 and sets the countif for the starting rows correctly to [=countif(B$2:B2,B2)], but it sets any row after there is a row with a 1 in column D as the same formula, with the starting range at B$2.
function setCountifFormula() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Test");
var data = sheet.getDataRange().getValues();
for (var i = 1; i < data.length; i++) { //iterate through each row
var colBValue = data[i][1]; //get columnB in i
var colAValue = data[i][0]; // get date in i
var colDValue = data[i][3]; // get flag in i
var closestRow = 1; // empty variable
if( colDValue == "1") { //if columnD = 1
sheet.getRange(i+1,3).setValue(0); // set columnC = 0
} else {
for (var j = 1; j < data.length; j++) { //iterate through other rows
if (data[j][1] === colBValue && data[j][3] === "1") { // if columnB in j = ColumnB in i, and flag in row j = 1
var dateToCompare = data[j][0]; //set datetoCompare = date in row j
closestRow = j;
if (dateToCompare < colAValue) {
var range = "B$" + (closestRow + 1) + ":B" + (i + 1);
var formula = "=COUNTIF(" + range + ",B" + (i + 1) + ")";
sheet.getRange(i + 1, 3).setFormula(formula);
} else {
var range = "B$2:B" + (i+1);
var formula = "=COUNTIF(" + range + ",B" + (i+1) + ")";
sheet.getRange(i+1, 3).setFormula(formula);
}
}
}
if (closestRow === 1) {
var range = "B$2:B" +(i+1);
var formula = "=COUNTIF("+range +",B"+(i+1)+")";
sheet.getRange(i+1,3).setFormula(formula);
}
}
}
}
I can post the spreadsheet if needs be. If there is a different way without using scripts or COUNTIF, it'd be appreciated. Thanks!
I'm much better at scripting than complex formulas so here is an example of how I would do it.
function myCountif() {
try {
let values = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
values.shift(); // remove headers
let unique = values.map( row => row[1] );
unique = [...new Set(unique)];
let count = unique.map( row => 0 );
let counts = values.map( row => 0 );
values.forEach( (row,rIndex) => {
let cIndex = unique.findIndex( item => item === row[1] );
count[cIndex] = count[cIndex]+1;
counts[rIndex] = count[cIndex];
if( row[3] === 1 ) count[cIndex] = 0;
}
)
return counts;
}
catch(err) {
console.log(err);
}
}
Reference
Array.shift()
Array.map()
Set Object
Array.forEach()
Array.findIndex()
Arrow function =>
In my sheet column A is date and column B is time duration values, I want to find the dates which are repeated and sum up the corresponding time values of the repeated dates and show the sum in the last relevant repeated date. And delete all the other repeated dates. ie if 18/07/2019 is repeated 4 times i have to sum up all the four duration values and display the sum value in the 4th repeated position and delete the first three date 18/07/2019. I have to do this all those dates that are repeated. I have wrote code to my best knowledge
function countDate() {
var data = SpreadsheetApp.getActive();
var sheet = data.getSheetByName("Sheet5");
var lastRow = sheet.getLastRow();
var sh = sheet.getRange('A1:A'+lastRow);
var cell = sh.getValues();
var data= sheet.getRange('B1:B'+lastRow).getValues();
for (var i =0; i < lastRow; ++i){
var count = 0;
var column2 = cell[i][0];
for (var j =0; j < i; j++)
{
var p=0;
var column4 = cell[j][0];
if (column4 - column2 === 0 )
{
var value1 = data[j][0];
var value2 = data[i][0];
var d = value2;
d.setHours(value1.getHours()+value2.getHours()+0);
d.setMinutes(value1.getMinutes()+value2.getMinutes());
sheet.getRange('C'+(i+1)).setValue(d).setNumberFormat("[hh]:mm:ss");
sheet.deleteRow(j+1-p);
p++;
}
}
}
}
The copy of the sheet is shown
column C is the values I obtain through the above code AND column D is the desired value
After computing the sum I need to delete the repeated rows till 15 here
Answer:
You can do this by converting your B-column to a Plain text format and doing some data handling with a JavaScript dictionary.
Code:
function sumThemAllUp() {
var dict = {};
var lastRow = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].getLastRow();
var dates = SpreadsheetApp.getActiveSpreadsheet().getRange('A1:A' + lastRow).getValues();
var times = SpreadsheetApp.getActiveSpreadsheet().getRange('B1:B' + lastRow).getValues();
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn()).setNumberFormat("#");
for (var i = 0; i < dates.length; i++) {
if (!dict[dates[i][0]]) {
dict[dates[i][0]] = times[i][0];
}
else {
var temp = dict[dates[i][0]];
var hours = parseInt(temp.split(':')[0]);
var minutes = parseInt(temp.split(':')[1]);
var additionalHours = parseInt(times[i][0].split(':')[0]);
var additionalMinutes = parseInt(times[i][0].split(':')[1]);
var newMinutes = minutes + additionalMinutes;
var newHours = hours + additionalHours;
if (newMinutes > 60) {
newHours = newHours + 1;
newMinutes = newMinutes - 60;
}
dict[dates[i][0]] = newHours + ':' + newMinutes;
}
}
SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].getRange('A1:B' + lastRow).clear();
var keys = Object.keys(dict);
for (var i = 0; i < keys.length; i++) {
SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].getRange('A' + (i + 1)).setValue(keys[i]);
SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].getRange('B' + (i + 1)).setValue(dict[keys[i]]);
}
}
Assumptions I made:
There are a few assumptions I made when writing this, you can edit as needed but I figured I should let you know:
There are only dates in Column A and only times in Column B.
The times in column B are either Hours:Minutes or Minutes:Seconds. Either way, if the value to the right of the : hits 60, it adds one to the left value and resets.
The Sheet within the Spreadsheet is the first sheet; that which is returned by Spreadsheet.getSheets()[0].
References:
w3schools - JavaScript Objects
Spreadsheet.getSheets()
w3schools - JavaScript String split() Method
MDN web docs - parseInt() method
Google Sheets > API v4 - Date and Number Formats
I am looking to save products that are chosen in an invoice to another sheet in the same workbook when a payment method is selected. Here is a copy of the sheet. How the sheet works:
1) User places "x" into selection column in "Protocol Selection" (WORKING)
2) In the next sheet in the workbook "Patient Invoice", an invoice with bottle count is generated (WORKING)
3) I want the past invoices (with product, date, pill-count, etc.) to be copied over to the "Past Invoices" sheet when the "Method of Payment" is selected. This is a drop-down Data Validation cell. (NOT WORKING)
Is there a way to do this without a custom script? IF not, what is the script?
Probably not the most efficient method but it does work...
Script
function makePastInvoice() {
var patientInvoice = SpreadsheetApp.getActive().getSheetByName('Patient Invoice');
var pastInvoices = SpreadsheetApp.getActive().getSheetByName('Past Invoices');
// vistor details
var visitDate = patientInvoice.getRange("D3").getValue();
var patientName = patientInvoice.getRange("G5").getValue();
var doctorName = patientInvoice.getRange("I5").getValue();
var programLength = patientInvoice.getRange("I3").getValue();
var invoiceNumber = patientInvoice.getRange("G3").getValue();
var total = patientInvoice.getRange("K30").getValue();
var paymentMethod = patientInvoice.getRange("D5").getValue();
var visitorDetails = [visitDate, patientName, doctorName, programLength, invoiceNumber, total, paymentMethod];
var lastRow = pastInvoices.getLastRow();
var currentRow = lastRow + 1;
// set values of patient details to Past Invoices sheet
for (var i = 0; i < visitorDetails.length; i++) {
pastInvoices.getRange(currentRow, i+1).setValue(visitorDetails[i]);
}
// 15 possible max items (per Past Invoices)
var productIDs = patientInvoice.getRange("A8:A22").getValues();
// count number of products
var productCount = 0;
for(var i = 0; i < productIDs.length; i++) {
if (productIDs[i] > "") {
productCount++;
}
}
// Patient Invoice
var firstItemRow = 8;
// Past Invoices
var firstItemCol = 8 // column H
// loop through purchases
for(var i = 0; i < productCount; i++) {
// purchase details
var productName = patientInvoice.getRange(firstItemRow, 2).getValue(); // col B
var dosage = patientInvoice.getRange(firstItemRow, 5).getValue(); // col E
var numBottles = patientInvoice.getRange(firstItemRow, 6).getValue(); // col F
var purchaseDetails = [productName, dosage, numBottles];
// set values of purchase details to Past Invoices sheet
for(var j = 0; j < purchaseDetails.length; j++) {
pastInvoices.getRange(currentRow, firstItemCol).setValue(purchaseDetails[j])
firstItemCol ++;
}
firstItemRow++;
}
}
Trigger
I'm sure there's a way to trigger the script when the payment method changes or any other method of choice but I created a "submit" trigger with a drawing and assigned it to the makePastInvoice function.
Also, you'll probably want to change the "Visit Date" column's format to date.
I am working on creating a schedule in Google Sheets and I have just one last task that I cannot figure out. Here is an example of what I would like to accomplish.
For lines 8-12 (which is for Employees 1-5) I need to have a script that looks for every cell that contains either A or A-10. If these A or A-10 shifts fall on the 1-15 (days of the month are in row 5...pictured below) they should be changed to Aa or Aa-10. If they fall on the 16th-end of the month they should be Ap or Ap-10. The same will be the case with the R and I shifts.
Here is a screen shot of the Google Sheet from the example I gave above:
Any help would be greatly appreciated! Thanks!
Here is a script that will do those changes. It only changes rows 8 - 12 and assumes the dates are in row 5.
function changeValues() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getActiveSheet();
var range = sheet.getRange("8:12");
var dates = sheet.getRange("5:5").getValues();
var values = range.getValues();
for(var row = 0; row < values.length; row++) {
for(var column = 1; column < values[row].length; column++) {
var day = Number(dates[0][column].replace(/[^0-9]/g, ""));
var value = values[row][column];
if(day >= 1 && day <= 15) {
if(value === "A") value = "Aa";
else if(value === "A-10") value = "Aa-10";
} else if(day > 15) {
if(value === "A") value = "Ap";
else if(value === "A-10") value = "Ap-10";
}
values[row][column] = value;
}
}
range.setValues(values);
}
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);
}