Google form to generate a unique value for each form submission - google-apps-script

On Google form submission, I am trying to get a unique patient id in column A automatically whenever there is a response being submitted. You can also see the formula in the formula bar.
As of now, I am able to do it with the following google script but due to the trigger limit, it is not getting fulfilled as the COVID response is quite higher and requests are keep coming in.
function myFunction1() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var lr=sheet.getLastRow()
sheet.getRange(lr,1).setFormula('="PID"&row()-1');
}
If I add the formula to all cells in column A of this sheet, every time a new row is being inserted on form submission and the formula is NOT available for that row by default.
So, Is there a way that I can automate generating the PID automatically.? or any other smart ideas without the trigger option?

Alternative ARRAYFORMULA in cell A1:
=arrayformula({"Patient Unique ID #";if(B2:B<>"","PID"&row(A2:A)-1,iferror(1/0))})
"Patient Unique ID #" puts the title (Patient Unique ID #) in cell A1, then the ; returns the line.
If B is not empty then generate the unique ID as per "PID" and a unique number based on ROW() less 1. If B is empty, then nothing iferror(1/0).

In Google Sheets, this sort of thing is usually done with an array formula. I would recommend that you leave the 'Form Responses 1' sheet as is and insert the timestamps in a new sheet with something like this:
cell A1:
=arrayformula(
ifs(
row('Form Responses 1'!A1:A)) = 1, "Patient ID",
isnumber('Form Responses 1'!A1:A), row('Form Responses 1'!A1:A) - 1,
true, iferror(1/0)
)
)
cell B1:
={ 'Form Responses 1'!A1:Z }
If you absolutely want to insert the Patient ID column directly in the form responses sheet, you may want to place it on the right after the columns that get updated by the form.
An array formula is more reliable than using a function that runs on a trigger, but do note that the patient IDs will be dynamic and only remain valid as long as no rows are inserted or deleted in the data. The same is true with your current approach.
To get static IDs, use an on form submit trigger that writes an ID on a row when that row is first submitted. See the insertUniqueId_ script for one example — it runs on an on edit trigger, but can be modified to run on an on form submit trigger.

Related

Google Appscript partial vlookup

I am very new to appscript and any help on the below query will really be helpful.
I am having data in Sheet1 in column A and Column B, now I want to design an appscript which get the below job done.
(1) Sheet1 Column A has Roll number along with student name i.e. Roll No.*Name, column B has actual value (these both columns are user input).
(2) Sheet2 has two columns Roll No. and Dummy value.
(3) In Sheet1 column C, I want the dummy value from Sheet2 column B by matching the Roll number of both sheets, however, I want value only in those rows in which actual value (column B) in Sheet1 is blank.
For reference I am sharing the link of the sheets.
https://docs.google.com/spreadsheets/d/1vI22QCmixKe3aoWMLODTFzt7pNXIKO3pjXS4mT6GHT0/edit#gid=524973836
I had checked different question on this community but not found a solution to my query.
Any help on above will really be appreciated.
Regards
Assume you want to put down 'dummy values' in Sheet1 column C if column B is empty, and that so call 'dummy value' need to has its 'Roll No.' value matched with the number of the front part of 'Roll No.#Name' column in sheet1:
Solution - 1. in-app lookup function:
Put this code into Column C2 in sheet1.
This code lookup for matches of 'Roll No.' from sheet2 column A whenever there Column A is not empty and Column B is empty in sheet1.
=ArrayFormula(
IFS(
$A$2:$A="","",
$B$2:$B<>"","",
TRUE,XLOOKUP(INDEX(SPLIT($A$2:$A,"*"),,1),sheet2!$A:$A,sheet2!$B:$B)
)
)
Solution - 2. write a custom function in apps-script:
in-app call:
apps-script js code:
this solution create a custom function called 'MYLOOKUP()' with apps-script, and call the function form the spreadsheet.
// apps-script:
function MYLOOKUP(data1,data2) {
return data1
.map(([rollNo_Name,value]) => {
return (rollNo_Name !== '' && value === '') ?
data2.find(([rollNo,]) => rollNo_Name.split('*')[0] == rollNo)[1] :
''
});
}
in-app call:
=MYLOOKUP(A2:B,sheet2!A:B)
There are a lot other solution to your question if you mention apps-script, such as...
instead of calling the custom function in-app, you can instead create a custom menu to run the function when you click on it,
or add simple trigger so that every time the spreadsheet is edited, the function will be run one time.
you can even create a web ui for this thing to run, etc.

Using "=TRANSPOSE()" for my column headers, how do I keep data linked when I insert a new row in the original data?

I have one sheet which is my data in which I list my events and their dates. These are then inserted on a separate sheet where volunteers can use checkboxes for their availability. Now if I reorder my events in the data-sheet then the headers in the availability sheet will move, but the availability will not move with them. Any way to fix this?
Here is an example sheet:
https://docs.google.com/spreadsheets/d/1tTVMOCKnLT2dRhKDBMV74LNMYOQvxKv3AA8egHCVG78/edit?usp=sharing
This is a simplified example from my actual problem, but I was wondering if there is a good way to keep this data linked? I would like to be able to re-order the data in the "Events Data" sheet while keeping the correct availability under each event in the "Availability sheet". Currently, one moves and the other one is static.
How I've done this using Google Apps Script in a similar situation (in my case, associating a "memo" column with rows producted by QUERY). Essentially, what we do is synthesize our dynamic display (with QUERY in my case and TRANSPOSE in yours) from static sources (other sheets), and use Google Apps Script to move data entered on the dynamic sheet to the appropriate static sheet, where we can easily retrieve it and render it appropriately.
The only information stored on the dynamic sheet is that used for rendering it (TRANSPOSE, VLOOKUP, whatever works for your situation). When the user edits that sheet, we will take the value of their edit and immediately move it to a static sheet using onEdit(). This information from the static sheet will then be rendered onto the dynamic sheet, regardless of how its rendering changes.
First, create a sheet where you will store the true values for each row.
Second, use an arrayformula vlookup (or hlookup) to search your storage sheet based on a unique identifier from your transposed sheet, e.g.
=ARRAYFORMULA(IFNA(VLOOKUP(A2:A, Storage!A2:B, 2, 0), ""))
Third, using the Script Editor, add the following to your onEdit() function in Google Apps Script:
if (/* use e.range here to check that you're in an appropriate location... */) {
//this section should execute on our 'dynamic' sheet, after the user edits it
var unique_id = SpreadsheetApp.getActiveSheet().getRange(e.range.getRow(), 1).getValue();
// the 'getRange' here is looking from where the user edited to a unique identifier
// associated with this record; this could potentially be built on to reference multiple unique identifiers.
// For instance, this would be the cell with the text, e.g., unique_id = 'Event A'
// and the script could be extended to ALSO pick up, e.g. "edited_person", etc.
var storagesheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Storage');
var findid = storagesheet.createTextFinder(unique_id).findNext();
// this will search your storage sheet to see if there is an existing entry associated with the unique ID
if (findid) {
storagesheet.getRange(findid.getRow(), 2, 1, 2).setValues([[e.value, e.user]]);
// if there is, then we update it ON THE STORAGE SHEET with the new contents of the cell
} else {
storagesheet.appendRow([unique_id, e.value, e.user]);
// otherwise, we create one with the contents of the cell.
//This can likewise be expanded; create more cells, update more sheets, create new sheets, whatever else may be needed
}
e.range.clearContent();
if (e.range.getRow() === 2) { //or wherever your array formula is, again we are assuming this part of the onEdit() should only occur for edits of the dynamic sheet
//making sure we don't lose array formula if the user writes in this field
e.range.setFormula("=ARRAYFORMULA(IFNA(VLOOKUP(A2:A, Storage!A2:B, 2, 0), \"\"))")
}
}
These are based on vertically stored/displayed information, and only store a single field for each unique ID, but you can extrapolate on that pretty easily to match based on both the event and person's value, store multiple values for each event, etc. according to your needs.

Insert new row and move data based on cell text

Trying to automate a Google sheet to save time for a team member. I have zero scripting experience. I am trying to insert a row based on a "Yes" result from column K in a Google form submission sheet and then move data in cells L:P to the new row, all without messing up the query functions that are pulling this data.
Is this possible?
Appending selected data from a form submission
function onFormSubmit(e) {
var ss=SpreadsheetApp.openById('SpreadsheetID');
var sh=ss.getSheetByName('Sheet Name')
if(e.values[11]=="Yes") {
var nV=e.values.slice(11,16);
ss.appendRow(nV);
}
}
Since you said that you have zero scripting experience, I should warn you that you cannot run this function without supplying the event object and personally I would never append new rows to a linked sheet. I would append the rows to another sheet instead. There have been problems with the onFormSubmit trigger lately which causes spurious additional triggers as described here.

How to catch last update in sheet

I have three Google Forms which submit data to different sheet in Spreadsheet. Google Forms write time stamp in first column when data is submitted. My triggered function send the email notification on submit data to spreadsheet from GForm. I need a different content of notification depending on the sheet of the spreadsheet where data is submitted.
I think about this:
get last update of spreadsheet
switch first sheet to action sheet
find the date equals the last update data in action sheet
if date is not founded, switch to next sheet and find last update date
...
...
if date is founded
get name of action sheet
send content-associated notification
So, think it's too difficult. Of course I can create many spreadsheet with many scripts and each script will send different notification, but it is not ergonomic.
Thank you!
In the event coming from your forms you can get the timeStamp and the row number where the response is recorded. With both criteria I guess it shoudn't be too hard to find the right sheet, just examine this row number in every sheet and compare the time values, if there is a match then you get it !
You can get the values in the eventinfo like this:
Logger.log('row = '+e.range['rowEnd'] + ' timestamp = '+e.namedValues['Timestamp'] )
I didn't test further so please update here is it worked as expected (or not).
Maybe you can add an onEdit trigger:
function onEdit() {
var s = SpreadsheetApp.getActiveSheet();
var sheet_name = s.getName();
//you can also add a code to get row and col, timestamp, etc.
}
Note: haven't tested this code. I assume this is what you want.

copy spreadsheet rows from one sheet to another conditional on name of sheet

Please help this non-expert -- his job depends upon it.
I have a Google Form that feeds answers into a sheet on a Google Spreadsheet -- let's call it the "main sheet." One of the form's questions asks for a job number. I have been able to figure out how to take each unique job number respondents have entered and create a sheet by that name in the same spreadsheet. I even have copied the headers for the form's answers at the top of each of the new sheets.
What I cannot get to work is rifling through all of the answers in the main sheet, sans the column headers, and copy the rows to the sheet names based on a conditional match with the job number value equaling the sheet name value. So if someone applies for job number 65, that response gets copied from the main sheet to sheet with the name "65."
For starters, I may have the loops set up incorrectly, trying to exclude the header in the main sheet and creating the array of all of the sheet names.
But a second problem I have is that I need to use the variables for both the sheet name value and the job number value. I need to be able to account for an ever increasing job numbers. Staff do not want to have to create a new sheet with every new job number -- they want that done automatically when a user fills in a new job number.
I am happy to share my work, as it is, with anyone who can help point me in the right direction.
+++
Solved, so I yanked down the link to the spreadsheet. In spite of myself, it appears, this community was able to help me out. Thanks.
This function that I wrote will help you find your job number:
function searchColumn(value, rangeValues) {
for (var i = 1; i < rangeValues.length; ++ i) {
if (rangeValues[i] == value) return i;
}
return -1;
}
Give it an array of values (usually by Range.getValues()), and the value you want to search and it will return the row number where that value was found. If it didn't find it, it returns -1.
example:
// Gets all data in the first column of the sheet
var valuestoSearch = mySheet.getRange(1, 1, sheet.getLastRow()).getValues();
// then
var rowNum = searchColumn(job_number, valuesToSearch);
Now that you have the row number, you can use:
var rowData = mySheet.getRange(rowNum, 1, 1, mySheet.getLastColumn()).getValues();
This will give you all the values in that row up to the last column in the sheet.
Once you have that data, you can copy it to another sheet by using:
var jobSheet = mySpreadsheet.getSheetByName(job_number);
jobSheet.getRange(jobSheet.getLastRow() + 1, 1, 1, rowData.length).setValues(rowData);
Or something very similar to this. Then you may want to erase the data in the old spreadsheet so that you don't keep repeating the same operation. But that's up to you how you want to handle that. Take a good look at the documentation for SpreadsheetApp, as it has everything you're looking for and more :)