Is it possible for my Google sheet to automatically append a new formatted row to the end of a column when it fills up?
I would like to avoid having to manually create, say, 1000 pre-formatted empty rows in my sheet.
This is possible using Google Apps Script onEdit() trigger.
Try:
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var range = sheet.getRange(1, 1, sheet.getLastRow(), 3); //Range is set from COL A - COL C up to last row with data
var lastRow = sheet.getLastRow();
var val = range.getValues();
var lastVal = val[val.length - 1];
//Check if the edit happened within Columns A-C
var range = e.range
var col = range.getColumn();
//Set sheetname and cell range where the options are coming from
var options = ss.getSheetByName("Names").getRange("A2:A")
var dropdownCell1 = sheet.getRange(lastRow + 1, 1);
var dropdownCell2 = sheet.getRange(lastRow + 1, 2);
if (col>= 1 && col <= 3 && !lastVal.includes('')) { //checks if the last row of data is filled
//Sets the data validation rule to the cells
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(options)
dropdownCell1.setDataValidation(rule)
dropdownCell2.setDataValidation(rule)
}
}
Result:
Explanation:
This uses onEdit() trigger which runs every time you make edit to a cell value. This then checks if the edit happens within Columns A-C and checks if the last row from Columns A-C are filled. It then adds the data validation rule to the cells. The dropdown comes from a sheet called 'Names' and the options are listed there.
I just did not add a code to reverse it if a data is deleted. You may edit the range if you are using different columns. Hope this helps!
Format the sheet down to the last row, as tested here in this GIF using Sequence(20), Sequence(2000).
I have a google sheet like this example to track scores for disc golf:
https://docs.google.com/spreadsheets/d/1uxDFXg2kivZWKICeVklugyXH1OWqsq_s5qXZYzgHkt8/edit?usp=sharing
It works great for tracking day to day scores but it would be really awesome to have a single sheet at the beginning that could say everyones total scores that they have gotten. Also note that the names may not be the same in each sheet.
So in this example I would want to have a new sheet that would automatically calculate the scores from the other sheets and show:
Mike 67,71,65
George 83,70
Phillip 79,72,65
John 66,71
Henry 69
I am very unfamiliar with excel formulas and have been struggling to get this started. Any help would be greatly appreciated.
You can use a Google Apps Script to accomplish what you are looking for. The idea of the code is that it will iterate over every Sheet in your Spreadsheet, gather all the players and all their values, and finally create a summary and put it into the "Summary" sheet (that sheet must exist in your Spreadsheet, with strictly the same name):
function updateSummary() {
var sheets = SpreadsheetApp.getActive().getSheets();
var summarySheet = SpreadsheetApp.getActive().getSheetByName('Summary');
var allScores = {};
for (var i=0; i<sheets.length; i++) {
if (sheets[i].getName() == 'Summary') continue;
var nColumns = sheets[i].getLastColumn();
var names = sheets[i].getRange(1, 1, 1, nColumns).getValues()[0];
var scores = sheets[i].getRange(20, 1, 1, nColumns).getValues()[0];
for (var j=0; j<nColumns; j++) {
var currentName = names[j];
var currentScore = scores[j];
if (!allScores.hasOwnProperty(currentName))
allScores[currentName] = [];
allScores[currentName].push(currentScore);
}
}
summarySheet.clear();
for (var key in allScores) {
var row = [key].concat(allScores[key]);
summarySheet.appendRow(row);
}
}
This will create, with the data given, the following data in the "Summary" Sheet:
Instead, if you prefer to have two columns as you described in your question (with the second one holding every score separated by commas), you would simply need to replace the last for-loop in the code above for the following one:
for (var key in allScores) {
var row = [key].concat(allScores[key].join(','));
summarySheet.appendRow(row);
}
Finally, you can create an image in the "Summary" sheet which can serve as a button to run the script. To do so:
Within your Sheet, click on Insert>Image>Image over cells.
Select any image of your choice.
Select the newly created image and click on the three dots icon that appears on the top-right corner of the image.
Click on "Assign script" and put the function name (in this case, updateSummary) and click on OK.
try:
=QUERY(TRANSPOSE({
'MikeGeorgePhillipJohn 121519'!A1:D20,
'MikeGeorgePhillipJohn 122019'!A1:D20,
'MikeJosephPhillipHenry 122719'!A1:D20}),
"select Col1,sum(Col20)
where Col1 is not null
group by Col1
label sum(Col20)''", 0)
=ARRAYFORMULA(SPLIT(TRANSPOSE(QUERY(QUERY(TRANSPOSE({
'MikeGeorgePhillipJohn 121519'!A1:D20,
'MikeGeorgePhillipJohn 122019'!A1:D20,
'MikeJosephPhillipHenry 122719'!A1:D20}),
"select max(Col20)
where Col1 is not null
group by Col20
pivot Col1", 0),,999^99)), " "))
I have a complex Google Sheet query that works great except when a Google Sheet doesn't have as many columns as I use in my formula.
Here's what the formula looks like now:
=sum(filter(query(INDIRECT("'" & A2 & "'!$A$7:$23"),"select Q,R,S,T,U,V,W,X,Y,Z,AA,AB,AC,AD,AE,AF,AG,AH where B='"&C2&"'",0),query(INDIRECT("'" & A2 & "'!$A$7:$23"),"select Q,R,S,T,U,V,W,X,Y,Z,AA,AB,AC,AD,AE,AF,AG,AH where B='PROJECT'",0) >=date(2017,1,1),query(INDIRECT("'" & A2 & "'!$A$7:$23"),"select Q,R,S,T,U,V,W,X,Y,Z,AA,AB,AC,AD,AE,AF,AG,AH where B='PROJECT'",0) <=date(2017,12,31)))
It works great. But the problem is I run it against many worksheets and some don't have e.g. column AG,AH and end at AF at which point I get an error.
So what I need is a way to generate the string Q,R,S....[Name of Last Column in Sheet] and then I can use that instead of my hard-coded Q,R,S,T,U,V,W,X,Y,Z,AA,AB,AC,AD,AE,AF,AG,AH but I cannot figure out how to do that.
Any help is greatly appreciated. Thanks!
Per comments above, final formula was:
LEFT("Q,R,S,T,U,V,W,X,Y,Z,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR,AS,AT,AU,AV,AW,AX,AY,AZ,BA,BB,BC ",2*Columns(INDIRECT(A2&"!1:1"))-33+IF(Columns(INDIRECT(A2&"!1:1"))>26,Columns(INDIRECT(A2&"!1:1"))-26,0))
where column A contains the list of worksheets (tabs) in the Google Sheet. Put this in B2, and then copied it down. I am not marking this as the correct answer since others gave a correct formula-based answer but this did the trick for me.
This can be done with built-in functions:
On a helper sheet, let say you name it, helper, fill up range with letters A to Z, let say A1:A26
Let say that on B1 you write the following formula:
=ArrayFormula({A1:A26;TRANSPOSE(SPLIT(JOIN(",",SUBSTITUTE(QUERY(TRANSPOSE(A1:A26)&A1:A26,,27)," ",",")),","))}) . This will create a list of column letter headers.
On each new worksheet use columns(1:1) to get the total number of columns.
To get your string of column headers, then you could use something like :
JOIN(",",OFFSET(helper!B1,16,0,columns(1:1)-16))
QUERY(helper!B:B,"select B limit "&columns(1:1)-7&" offset 7")
NOTE:
If you decide to have only one helper sheet and use it on several spreadsheets, then use
QUERY(IMPORTRANGE(your_url,"helper!B:B"),"select Col1 limit "&columns(1:1)-7&" offset 7")
This can be done with script. Without seeing you spreadsheet, it is hard to know exactly what you need, but this should be close. I get the variables from Sheet1 and return the formula to Sheet1. Adjust the sheet name to fit your needs. This will look at your data sheets based on the variable sheet name determine the last column. Determine the column letters and build the string the query needs. It then sets the new query formula. I added a menu to run it from.
function onOpen() {
SpreadsheetApp.getActiveSpreadsheet().addMenu(
'Create Data', [
{ name: 'Run', functionName: 'formula' },
]);
}
function formula(){
var ss= SpreadsheetApp.getActiveSpreadsheet()
var s=ss.getSheetByName("Sheet1") //sheet where variables are
var sheet=s.getRange("A2").getValue()//variable sheet name
var sel=makeString(sheet) //get the select string of column letters
//Create formula and return to Sheet1 A3
var f= s.getRange("A3").setFormula('=sum(filter(query(INDIRECT("\'" & A2 & "\'!$A$7:$23"),"select '+sel+' where B=\'"&C2&"\'",0),query(INDIRECT("\'" & A2 & "\'!$A$7:$23"),"select '+sel+' where B=\'PROJECT\'",0) >=date(2017,1,1),query(INDIRECT("\'" & A2 & "\'!$A$7:$23"),"select '+sel+' where B=\'PROJECT\'",0) <=date(2017,12,31)))')
}
function makeString(sht){
var ss= SpreadsheetApp.getActiveSpreadsheet()
var s=ss.getSheetByName(sht)
var lc=s.getLastColumn()
var rng=s.getRange(1, 17, 1, lc).getValues()
var str=''
var ltr=[]
for(var i=17;i<rng[0].length+1;i++){
ltr[i]= columnToLetter(i)
str=ltr.join(',')
}
var str1=str.substr(17)
return str1
}
function columnToLetter(column)
{
var temp, letter = '';
while (column > 0)
{
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}
Let me know if you have any questions.
I have a google sheet that I would like to have generate an email alert when one column is greater than the other. Specifically, column F > column G. Here is what I have so far, any advice would be greatly appreciated, as I do not have much skill writing functions.
function readCell() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Watch list");
var value = sheet.getRange("F2").getValue();
var value1 = sheet.getRange("G2").getValue();
if(value>value1) MailApp.sendEmail('example#gmail.com', 'subject', 'message');
};
Currently this only attempts to compare cell F2 to cell G2. Is there a way to make the function compare the entire F column against column G, and generate an email for each individual case where Fx > Gx ?
Thank you!!
You have to loop all over the range.
first instead of getting the content of one cell you'll need to get the content of all the column:
var value = sheet.getRange("F2").getValue();
become that
var values = sheet.getRange("F2:F").getValues();
(same for value1)
then you need to create an empty table that will collect the results:
var results = [];
and now you need to loop throught all the values:
for(var i=0;i<values.length;i++){
//do the comparaison and store result if greater for example
}
then you may send the result.
all put together it give something like that:
function readCell() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Watch list");
var values = sheet.getRange("F2:F").getValues();
var value1s = sheet.getRange("G2:G").getValues();
var results = [];
for(var i=0;i<values.length;i++){
if(values[i]<value1s[i]){
results.push("alert on line: "+(i+2)); // +2 because the loop start at zero and first line is the second one (F2)
}
}
MailApp.sendEmail('example#gmail.com', 'subject', results.join("\n"));
};
If you want to trigger that function automatically you'll also need to change the way you call the spreadsheet (instead of getActive.... you'll need to use openById)
I have a google form and I would like to sort it's responses in a separate sheet on google sheets. The results of the form look sort of like this.
Id Job
1 Shelving, Sorting
2 Sorting
1 Cleaning, Shelving
3 Customer Service
2 Shelving, Sorting
which I would like to format into
Id Jobs
1 Cleaning, Shelving, Sorting
2 Shelving, Sorting
3 Customer Service
Is there a formula I can use to accomplish this, noting that it ignores duplicates and groups the different ids? Ordering of the jobs does not matter.
Working example here.
The code is like:
=unique(transpose(split(join(", ",filter(B1:B10,A1:A10=1)),", ")))
where
filter(B1:B10,A1:A10=1) gives you all the B values where A = 1
join(",", filter(...)) joins the list with the ", " separator (e.g. "apple, orange" and "kiwi" becomes "apple, orange, kiwi"
split(join(...)) splits the list into an array (e.g. back to [apple, orange, kiwi]
transpose(split(...)) converts the horizontal list to vertical list
unique(transpose(...)) gives you the unique values (unique() only works with vertical list)
After this, you need to transpose then join the list
Note you must keep the separator consistent (e.g. always "," or ", ")
This is Apps Script code instead of a function. To use it, you will need to use the Tools menu, and open the script editor. Then select the function name from the drop down list, and then click the "Run" button.
To use this code, you need to have a source and a destination sheet. You will need to change the sheet names in the code to your sheet names. In this code, the source sheet is named 'Data'. You will need to change that to the name of your source sheet. In this code, the destination sheet is named 'Output', and is at the bottom of the code. This code gets data starting in row two, and writes the output data starting in row two. I tested it with your values and it works.
function concatCellData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('Data');
var colOneData = sh.getRange(2, 1, sh.getLastRow()-1, 1).getValues();
var colTwoData = sh.getRange(2, 2, sh.getLastRow()-1, 1).getValues();
var newData = [],
newDataColOne = [],
colOneValue,
existInNewData = false,
colB_One,
colB_Two,
dataPosition,
thisValue,
combinedArrays = [],
arrayTemp = [];
for (var i=0;i<colOneData.length;i+=1) {
colOneValue = colOneData[i][0];
dataPosition = newDataColOne.indexOf(colOneValue);
existInNewData = dataPosition !== -1;
if (!existInNewData) {//If it doesn't exist in the new data, just write the values
newDataColOne.push(colOneValue);
newData.push([colOneValue, colTwoData[i][0]]);
continue;
};
colB_One = [];
colB_Two = [];
combinedArrays = []
arrayTemp = [];
colB_One = colTwoData[i][0].split(",");
colB_Two = newData[dataPosition][1];
colB_Two = colB_Two.split(",");
var combinedArrays = colB_One.concat(colB_Two);
//Get unique values
for (var j=0;j<combinedArrays.length;j+=1) {
thisValue = combinedArrays[j].trim();
if (arrayTemp.indexOf(thisValue) === -1) {
arrayTemp.push(thisValue);
};
};
newData[dataPosition] = [colOneValue, arrayTemp.toString()]; //Overwrite existing data
};
ss.getSheetByName('Output').getRange(2, 1, newData.length, newData[0].length).setValues(newData);
};