I have a bunch of rows and I want to append duplicates except change two of the cells.
I need each person to have a row for 15000, 20000, 25000 for each 24 and 36 (if this makes sense?)
Input:
A B C D
1 15000 24 Susan Smith
2 15000 24 John Deer
Expected output
A B C D
1 15000 24 Susan Smith
2 20000 24 Susan Smith
3 25000 24 Susan Smith
4 15000 36 Susan Smith
5 20000 36 Susan Smith
6 25000 36 Susan Smith
7 15000 24 John Deer
8 20000 24 John Deer
9 25000 24 John Deer
10 15000 36 John Deer
11 20000 36 John Deer
12 25000 36 John Deer
I understand that I need to do a function that for each row copies and appends the row, but am unsure how this is done.
I believe your goal as follows.
You want to achieve the conversion in your question.
For example, when the values of the columns "C" and "D" are Susan and Smith, respectively, you want to put the following values to the Spreadsheet.
15000 24 Susan Smith
20000 24 Susan Smith
25000 24 Susan Smith
15000 36 Susan Smith
20000 36 Susan Smith
25000 36 Susan Smith
You want to achieve this using Google Apps Script.
In this case, I would like to propose the following flow.
Retrieve the values from the columns "C" and "D" from the source sheet.
Remove the empty rows.
Create an array for putting values using the values of 15000, 20000, 25000 and 24, 36 for the columns "A" and "B", respectively.
Put the values to the destination sheet.
When above flow is reflected to a Google Apps Script, it becomes as follows.
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet. And, please set the variables of srcSheetName and dstSheetName, and run the function of myFunction.
function myFunction() {
const srcSheetName = "Sheet1"; // Please set the source sheet name.
const dstSheetName = "Sheet2"; // Please set the destination sheet name.
const ss = SpreadsheetApp.getActiveSpreadsheet();
// 1. Retrieve the values from the columns "C" and "D".
const srcSheet = ss.getSheetByName(srcSheetName);
const values = srcSheet.getRange("C1:D" + srcSheet.getLastRow()).getValues();
// 2. Remove the empty rows.
const v = values.filter(([c,d]) => c && d);
// 3. Create an array for putting values using the values of `15000, 20000, 25000` and `24, 36` for the columns "A" and "B", respectively.
const colA = [15000, 20000, 25000];
const colB = [24, 36];
const res = v.reduce((ar, [c,d]) => {
colB.forEach(b => colA.forEach(a => ar.push([a, b, c, d])));
return ar;
}, []);
// 4. Put the values to the destination sheet.
const dstSheet = ss.getSheetByName(dstSheetName);
dstSheet.getRange(1, 1, res.length, res[0].length).setValues(res);
}
Note:
If you want to use above script as the custom function, you can also use the following script. In this case, please copy and paste the following script to the script editor of Spreadsheet. And, please put the custom function of =SAMPLE(C1:D) to a cell. By this, the result values are obtained.
const SAMPLE = values => values
.filter(([c,d]) => c && d)
.reduce((ar, [c,d]) => {
[24, 36].forEach(b => [15000, 20000, 25000].forEach(a => ar.push([a, b, c, d])));
return ar;
}, []);
References:
getValues()
setValues(values)
reduce()
forEach()
Custom Functions in Google Sheets
Related
I was wondering which Google Apps Script function may help me to split a Google Sheets cell value into n parts (given a separator) and replicate the whole row as different occurrences for that split. So, f.i., given this table:
Name
Country
Sport
John
USA
Basketball_Golf_Tennis
Mary
Canada
Tennis_Golf
the desired output should be:
Name
Country
Sport
John
USA
Basketball
John
USA
Golf
John
USA
Tennis
Mary
Canada
Tennis
Mary
Canada
Golf
In this example, the separator is the char _
You could probably do this with a regular spreadsheet formula (lookout for incoming solution from Player0 who will probably point out something I should have thought of... ), but since you asked for an app script solution, this works:
/**
* Splits data
*
* #param {array} theRange The range of data.
* #param {string} theSplitter The text used to split.
* #return the new table
* #customfunction
*/
function goUSA(theRange, theSplitter) {
const splitColumn = 2;
var result = [];
for (r = 0; r < theRange.length; r++) {
var aRow = theRange[r];
//skips empty rows, enabling ability to select entire column
if (aRow.join('') != '') {
var tempSplit = aRow[splitColumn].split(theSplitter);
for (q = 0; q < tempSplit.length; q++) {
result.push([aRow[0], aRow[1], tempSplit[q]]);
}
}
}
return result;
}
see:
=INDEX(QUERY(SPLIT(FLATTEN(IF(IFERROR(SPLIT(C1:C, "_"))="",,
A1:A&""&B1:B&""&SPLIT(C1:C, "_"))), ""), "where Col2 is not null", ))
Splitting Column 3
function brkaprt() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const osh = ss.getSheetByName("Sheet1");
osh.clearContents();
const vs = sh.getRange(2,1, sh.getLastRow() - 1, sh.getLastColumn()).getValues();
let obj = {pA:[]};
let o = vs.reduce((ac,[a,b,c],i) => {
c.split("_").forEach(e =>ac.push([a,b,e]) )
return ac;
},[]);
o.unshift(["Name","Country","Sport"]);
Logger.log(JSON.stringify(o));
osh.getRange(1,1,o.length,o[0].length).setValues(o);
}
Execution log
10:56:15 AM Notice Execution started
10:56:16 AM Info [["Name","Country","Sport"],["John","USA","Basketball"],["John","USA","Golf"],["John","USA","Tennis"],["Mary","Canada","Tennis"],["Mary","Canada","Golf"]]
10:56:17 AM Notice Execution completed
A
B
C
1
Name
Country
Sport
2
John
USA
Basketball
3
John
USA
Golf
4
John
USA
Tennis
5
Mary
Canada
Tennis
6
Mary
Canada
Golf
Given this table schema:
Col_France
Col_Argentina
Col_Croatia
Col_Morocco
x
x
x
x
x
x
x
x
I want to create a Google Script that rearranges the columns so the order is always:
Col_Argentina -> Column 1
Col_France -> Column 2
Col_Croatia -> Column 3
Col_Morocco -> Column 4
Because the original column orders of the given table is not always as described above, I cannot simply use:
var sheet = SpreadsheetApp.getActiveSheet();
// Selects Col_France.
var columnSpec = sheet.getRange("A1");
sheet.moveColumns(columnSpec, 2);
and so on... In other words, the table schema can possibly be:
Col_Morocco
Col_Croatia
Col_France
Col_Argentina
x
x
x
x
x
x
x
x
but the desired outcome should always be the defined above. The script should be scalable. In the future, more than 4 columns should be rearranged.
My approach would be:
Define the range of columns to rearrange (they are all together)
For the first column, get the value of the column header
Depending on the value, move the column to a predefined index
Move to the next column and repeat
Iterate until end of range
Can somebody please point me to the required functions?
In your situation, when moveColumns is used, how about the following sample script?
Sample script:
function myFunction() {
var order = ["Col_Argentina", "Col_France", "Col_Croatia", "Col_Morocco"]; // This is from your question.
var sheet = SpreadsheetApp.getActiveSheet();
var obj = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0].reduce((ar, h, i) => [...ar, { from: i + 1, to: order.indexOf(h) + 1 }], []).sort((a, b) => a.to > b.to ? 1 : -1);
for (var i = 0; i < obj.length; i++) {
if (obj[i].from != obj[i].to) {
sheet.moveColumns(sheet.getRange(1, obj[i].from), obj[i].to);
obj.forEach((e, j) => {
if (e.from < obj[i].from) obj[j].from += 1;
});
}
}
}
When this script is run, the columns are rearranged by order you give. In this case, the text and cell format are also moved.
When moveColumns(columnSpec, destinationIndex) is used, the indexes of columns are changed after moveColumns(columnSpec, destinationIndex) was run. So, please be careful about this. In the above script, the changed indexes are considered.
References:
moveColumns(columnSpec, destinationIndex)
reduce()
forEach()
Order Columns:
function ordercols() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const [h,...vs] = sh.getDataRange().getValues();
const idx = {};
h.forEach((h,i) => idx[h]=i);
const o = vs.map(r => [r[idx['COL4']],r[idx['COL3']],r[idx['COL2']],r[idx['COL1']]]);
sh.clearContents();
o.unshift(['COL4','COL3','COL2','COL1']);
sh.getRange(1,1,o.length,o[0].length).setValues(o);
}
Data:
COL1
COL2
COL3
COL4
24
5
2
9
16
0
13
18
22
24
23
16
12
12
4
17
6
20
17
14
7
13
4
2
2
20
4
22
3
5
3
4
16
5
7
23
ReOrdered:
COL4
COL3
COL2
COL1
9
2
5
24
18
13
0
16
16
23
24
22
17
4
12
12
14
17
20
6
2
4
13
7
22
4
20
2
4
3
5
3
23
7
5
16
After combing several cells into an array, I am checking over that array to confirm that the User has not missed any inputs through a quick check for empty cells. It works beautifully every time, unless some of those values are the number 0. When any input is 0, it triggers the flag asking the User to enter a value into each cell. 0 should be an acceptable value for the purposes of this tool, so I want to allow that while not allowing missed (blank) cells. I've been digging through documentation from Apps Script and searching StackOverflow for similar issues, but I've come up blank so far.
Below is the code I have for this function.
console.log(sourceVals)
const anyEmptyCell = sourceVals.findIndex(cell => cell == "");
if(anyEmptyCell !== -1){
const ui = SpreadsheetApp.getUi();
ui.alert(
"Input Incomplete",
"Please enter a value in ALL input cells before submitting",
ui.ButtonSet.OK
);
return;
Just change the comparison operator:
function testie() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const vs = sh.getRange("A1:A23").getValues().flat();
const anyEmptyCell = vs.findIndex(cell => cell === "");//just change the comparison operator
if (anyEmptyCell !== -1) {
const ui = SpreadsheetApp.getUi();
ui.alert(
"Input Incomplete",
"Please enter a value in ALL input cells before submitting",
ui.ButtonSet.OK
);
}
}
My Data:
A
1
COL1
2
17
3
14
4
1
5
19
6
14
7
1
8
11
9
3
10
16
11
0
12
19
13
8
14
2
15
15
16
10
17
19
18
12
19
2
20
1
21
11
22
23
It finds an empty cell at index 21 which is row 22 and there is a zero in the column
I have a Google Sheet that is being used to track applicant interview data. I am trying to find the Round Average Score for each candidate based on their Interview Round and Round score. I figured out how to gather this data with a query function but for this use case in particular it has to be done in a script.
Here is an example of the sheet
Any help would be greatly appreciated.
Average of Average Scores
function lfunko() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const vs = sh.getRange(2, 1, sh.getLastRow() - 1, sh.getLastColumn()).getValues();
let co = { pA: [] }
vs.forEach((r, i) => {
let p = `${r[0]}/${r[2]}`;
if (!co.hasOwnProperty(p)) {
co[p] = { cnt: 1, sum: r[4], idx: i }
co.pA.push(p);
} else {
co[p].cnt += 1;
co[p].sum += r[4];
}
});
let vo = vs.map((r, i) => {
let p = `${r[0]}/${r[2]}`;
if (i == co[p].idx) {
return [co[p].sum / co[p].cnt];
} else {
return [''];
}
})
sh.getRange(2, 6, vo.length, vo[0].length).setValues(vo);
}
Ouput:
Candidate
Position
Interview Round
Panelist
Round Score
Round Average Score
Bob
Tester
First
Jon
3
4
Bob
Tester
First
Janet
4
Bob
Tester
First
Joe
5
Bob
Tester
Second
Sal
4
3.333333333
Bob
Tester
Second
Riley
3
Bob
Tester
Second
Tae
3
Bob
Tester
Final
Wanda
5
4.666666667
Bob
Tester
Final
Kelly
4
Bob
Tester
Final
Arnold
5
Al
Senior Tester
First
Ben
2
3
Al
Senior Tester
First
Tori
3
Al
Senior Tester
First
Harry
4
Al
Senior Tester
Second
Kate
4
3.666666667
Al
Senior Tester
Second
Wendy
5
Al
Senior Tester
Second
Carl
2
Al
Senior Tester
Final
Sam
5
4
Al
Senior Tester
Final
Jake
3
Al
Senior Tester
Final
Troy
4
If you need to get the data as permanent static values that will not change later even if the source data gets modified, you can still use a query() formula to get the results, and then use a short script to replace the formula and its results with static values. To try it out, Insert > Sheet and use this:
=query(sumAve!A1:E, "select A, B, avg(D) where D is not null group by A, B", 1)
/**
* Replaces formulas with values in the active sheet.
*/
function replaceFormulasWithValuesInActiveSheet() {
const wholeSheet = SpreadsheetApp.getActiveSheet().getDataRange();
wholeSheet.setValues(wholeSheet.getValues());
}
I've been working on a time in/time out google sheet with a script. My original question was how to share a sheet to a user(let's call them client) where the Client can edit the sheet that had an onEdit trigger that would then set values into another sheet that the Client did not have permission to use.
I first tried just sharing a spreadsheet and making one of the sheets in that spreadsheet locked. But this prevented the trigger from running correctly when used by the client. So I asked about it and someone suggested using an installable edit trigger on the client sheet. And have the information be appended to a new spreadsheet. However, I don't think this is what I wanted because the way the code works needs the clients to not be able to edit the information that will be appended, not because I don't want false information but because if they get rid of certain columns, the script doesn't know what to do.
But it did give me an idea, instead of having one spreadsheet with two sheets, just have 2 spreadsheets with 1 sheet each. Much less likely for the people using the client account to find the shared spreadsheet vs finding the sheet in the open spreadsheet they are using.
So now to show you some code. Sorry I feel I have to show all of it due to any small thing that could be wrong I am over looking.
Host Spreadsheet Id is the Id of the spreadsheet the information is being logged into. This is the information I don't want being edited by the client.
ACTIVE refers to the name of the sheet the client is clicking on and editing. PASSIVE refers to the name of the sheet that is collecting information. Originally these two sheets were on the same spreadsheet.
function setValue(cellName, value) {
SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).setValue(value);
}
function getCurrentRow() {
var currentRow = SpreadsheetApp.getActiveSheet().getActiveSelection().getRowIndex();
return currentRow;
}
function getValue(cellName) {
return SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).getValue()
}
function getNextRow() {
var sas = SpreadsheetApp.openById("Host Spreadsheet Id");
var ss = SpreadsheetApp.setActiveSpreadsheet(sas)
return SpreadsheetApp.getActiveSpreadsheet().getSheetByName('PASSIVE').getLastRow() +1;
}
function getLasttRow() {
var sas = SpreadsheetApp.openById("Host Spreadsheet Id");
var ss = SpreadsheetApp.setActiveSpreadsheet(sas)
return SpreadsheetApp.getActiveSpreadsheet().getSheetByName('PASSIVE').getLastRow();
}
function addRecord(a, b, c, d) {
var sas = SpreadsheetApp.openById("Host Spreadsheet Id");
SpreadsheetApp.setActiveSpreadsheet(sas)
var row = getNextRow();
setValue('PASSIVE!A' + row, a);
setValue('PASSIVE!B' + row, b);
setValue('PASSIVE!C' + row, c);
setValue('PASSIVE!D' + row, d);
}
/// this function is to find the row number(s) that match the criteria in
/// the timeIn() function so that the time in date can be placed
/// directly to the right of the individual that signed out.
function findRows(c1,n1,c2,n2,c3,n3,name) {
var sas = SpreadsheetApp.openById("Host Spreadsheet Id")
SpreadsheetApp.setActiveSpreadsheet(sas)
var ss=SpreadsheetApp.getActiveSpreadsheet()
var sh=ss.getSheetByName(name);
var rg=sh.getDataRange();
var vA=rg.getValues();
var rA=[];
for(var i=0;i<vA.length;i++) {
if(vA[i][c1-1]==n1 && vA[i][c2-1]==n2 && vA[i][c3-1]==n3) {
rA.push(i+1);
}
}
return rA
}
function timeIn() {
var row = getCurrentRow()
var LocationA = getValue('ACTIVE!A' + row)
var LocationB = getValue('ACTIVE!B' + row)
var passiveRow = findRows(1,LocationA,2,LocationB,5,"",'PASSIVE');
Logger.log(passiveRow);
setValue('PASSIVE!E' + passiveRow, new Date().toLocaleString());
}
function places() {
var row = getCurrentRow()
addRecord(getValue('A' + row), getValue('B' + row), getValue('C' + row), new Date().toLocaleString());
}
function onEdit(e) {
var row = getCurrentRow()
var Location = getValue('ACTIVE!C' + row)
var LocationA = getValue('ACTIVE!A' + row)
var LocationB = getValue('ACTIVE!B' + row)
var passiveRow = findRows(1,LocationA,2,LocationB,5,"",'PASSIVE');
Logger.log(row)
Logger.log(Location)
Logger.log(LocationA)
Logger.log(LocationB)
Logger.log(passiveRow)
if(SpreadsheetApp.getActiveSheet().getName() !== "ACTIVE") return;
if(Location !== 'HOME' && Location !== "" && passiveRow == "") {
places();
Logger.log(passiveRow)
}
else if(Location !== 'HOME' && Location !== "" && passiveRow !== "") {
timeIn();
places();
Logger.log(passiveRow)
}
else if(Location === 'HOME' && passiveRow !== "") {
timeIn();
Logger.log(passiveRow)
}
}
So this was my attempt at turning what used to be 2 sheets in the same spreadsheet into 2 different spreadsheets. However, the onEdit(e) function doesn't work. If I manually run the timeIn() function, it works and the places() function works as well. Even goes and finds the last row of the spreadsheet it is importing information into. But alas, when they edit Column C, the column that is suppose to activate the onEdit(e) if else to work, it doesn't run the functions. What am I missing?
Let me give some visuals of what I'm trying to do.
Here is the sheet the clients should see. Column C has drop down boxes in each row full of locations.
First | Last | LOCATIONS
=================================
James | Carter | HOME
Kyle | Johnson | MALL
Micheal | Wilson | BANK
Sarah | Smith | HOME
Tray | Tin | CLINIC
John | Becks | HOME
Here would be the sheet collected by the Host Spreadsheet
First | Last | LOCATION | OUT | IN
=====================================================
Tray | Tin | CLINIC | 10:00 |
James | Carter | MALL | 12:30 | 1:30
Kyle | Johnson | MALL | 12:45 |
Micheal | Wilson | BANK | 01:00 |
James | Carter | POOL | 01:30 | 2:00
I hope this is a good enough visual to get the point across as to what I'm trying to accomplish. The code did work on the same spreadsheet, but now I'm trying to make it work on two in order to prevent the Client from messing with the Collected information. Because if something like this happens :
First | Last | LOCATION | OUT | IN
=====================================================
Tray | Tin | CLINIC | 10:00 |
James | Carter | MALL | 12:30 | 1:30
Kyle | Johnson | MALL | 12:45 |
Micheal | Wilson | BANK | 01:00 |
James | Carter | POOL | 01:30 | 2:00
Sarah | Smith | POOL | 01:30 |
Sarah | Smith | POOL | 01:31 |
You see Sarah Smith is on twice with no Sign in. This causes a bug because of how I get passive rows, it now sees 2 rows where "IN" is empty, so the function findRows() it gives me 2 values where I can only have 1 value in the function setValue() inside the timeIn() function.