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
Related
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());
}
Current Issue:
Hey everyone, appreciate any help here as I'm still beginning my journey in coding.
I'm trying to see if I can make a script that will:
Look for duplicates (in column D), and
delete any data from the following duplicates after the 1st match in columns E-L (see desired outcome if that doesn't make sense verbally).
The script would need to use the column header names (ex. "snacks") instead of hard-coded column references
*So for example, the script finds ABC001, deletes only the duplicates for ABC001 in the corresponding columns then moves on to ABC004 and performs the same action.
I'm not sure how to write a script that would do this, and keep going to find duplicates after the 1st set is found. I think I know how to do a for loop now, but it's not clear to me how to make it do a search loop and stop after it find the first match and keep going.
Current Data:
Desired Outcome:
Code so far below. I think I would need to incorporate something like JSmith showed in this example? Or would I need to incorporate some form of .length with the duplicate range in a for statement so that it can find the duplicates, get the # of them, and then only perform the action on everything past the 1st instance?
function duplicateRemoval() {
ss = SpreadsheetApp.getActive().getSheetByName('Sheet1');//gets sheet by name
const [aB,...cd] = ss.getDataRange().getValues();//literal assignment that assigns aB to the header array and the rest of the data to 'cd'
let column = {}
let iData = {};//index into the row array for each column header
aB.forEach((a,i)=>{column[a] = i+1;iData[a]=i});//building column and iData so that headers can move anywhere
}//let & forEach derived from (https://stackoverflow.com/questions/70101896/search-column-for-text-and-use-array-list-to-insert-text-in-another-cell) #Cooper
Raw Data:
Name
Owner
Snack
Transaction #
# of snacks requested
#2
#3
#4
#5
#6
#7
#8
Bill Example
Snacktown
celery
ABC001
4
1
2
3
4
5
6
4
Bill Example
Snacktown
celery
ABC001
4
1
2
3
4
5
6
4
Bill Example
Snacktown
celery
ABC001
4
1
2
3
4
5
6
4
Jane Doe
Snacktown
chips
ABC002
1
1
1
1
1
1
1
1
Jane Doe
Chipworld
chips
ABC003
1
1
1
1
1
1
1
1
Jane Doe
Chipworld
chips
ABC004
5
5
1
1
1
1
1
5
Jane Doe
Chipworld
chips
ABC004
5
5
1
1
1
1
1
5
Jane Doe
Chipworld
chips
ABC004
5
5
1
1
1
1
1
5
Jane Doe
Chipworld
chips
ABC004
5
5
1
1
1
1
1
5
Sources:
google app script array delete duplicate value from top
Google Script App Delete Duplicate Rows with a Specific Value in Specific Column in Google Sheet
How do I find and delete duplicate values in a range of cells while keeping the first occurrence of a duplicated value in Google Sheets?
Assuming transaction ids are always grouped, iterate through rows and delete all specified columns where previous transactionId is equal to current transactionId.
function duplicateRemovalOfColsToRemove() {
const transactionsHeader = 'Transaction #',
colsToRemoveHeaders = ['# of snacks requested', '#2'],//add column headers as necessary
ss = SpreadsheetApp.getActive().getSheetByName('Sheet1'), //gets sheet by name
range = ss.getDataRange(),
[headers, ...values] = range.getValues(),
colsToRemove = colsToRemoveHeaders.map((h) => headers.indexOf(h)),
transactionsIdx = headers.indexOf(transactionsHeader);
let currTransaction = '';
values.forEach((row) =>
row[transactionsIdx] === currTransaction
? colsToRemove.forEach((idx) => (row[idx] = ''))
: (currTransaction = row[transactionsIdx])
);
range.setValues([headers, ...values]);
}
It is unclear why you want to use a script here, as this seems doable with a plain vanilla spreadsheet formula. It is also unclear whether you really need to repeat the values in A2:D many times with nothing in columns E2:L.
To remove duplicate rows, and get just one copy of each unique transaction, choose Insert > Sheet and put this spreadsheet formula in cell A1:
=unique(Sheet1!A2:L)
To get the expected result you show, including rows that are mostly blank, use this:
=arrayformula(
{
Sheet1!A2:D,
array_constrain(
if(
Sheet1!D2:D <> Sheet1!D1:D,
Sheet1!E2:L,
iferror(1/0)
),
rows(Sheet1!E2:L), columns(Sheet1!E2:L)
)
}
)
To determine row uniqueness based on all columns A2:D instead of just the transaction ID in column D2:D, replace the if() condition with A2:A & B2:B & C2:C & D2:D <> A1:A & B1:B & C1:C & D1:D, inserting the proper sheet reference.
If you need a script you can try this:
function main() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var sheet = ss.getActiveSheet();
var range = sheet.getDataRange();
// get all data from the sheet
var data = range.getValues();
// get column headers
var headers = data.shift();
// get the list of transactions
var transactions = data.map(x => x[headers.indexOf('Transaction #')]);
// loop through all the transactions
for (let transaction of transactions) {
// get indexes of rows to process
var rows = transactions.map((t, row) => t === transaction ? row : '' ).filter(String).slice(1);
// process the rows
for (let r of rows) {
data[r][headers.indexOf('# of snacks requested')] = '';
data[r][headers.indexOf('#2')] = '';
data[r][headers.indexOf('#3')] = '';
data[r][headers.indexOf('#4')] = '';
data[r][headers.indexOf('#5')] = '';
data[r][headers.indexOf('#6')] = '';
data[r][headers.indexOf('#7')] = '';
data[r][headers.indexOf('#8')] = '';
}
}
// put the updated data back to the sheet
range.setValues([headers, ...data]);
}
Update
Here is the improved variant of the same code. It still loops through all the rows, but it skips already processed transactions:
function main() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var range = sheet.getDataRange();
var [headers, ...data] = range.getValues();
var transactions = data.map(x => x[headers.indexOf('Transaction #')]);
var cols_to_clean = ['# of snacks requested','#2','#3','#4','#5','#6','#7','#8'];
var processed_transactions = [];
for (let transaction of transactions) {
// skip already processed transactions
if (processed_transactions.includes(transaction)) continue;
var rows_to_clean = transactions.map((t, row) => t === transaction ? row : '' )
.filter(String).slice(1);
for (let r of rows_to_clean) {
cols_to_clean.forEach(c => data[r][headers.indexOf(c)] = '');
}
processed_transactions.push(transaction);
}
range.setValues([headers, ...data]);
}
Thanks to #TheMaster for the noted deficiencies.
Update 2
Sorry for spamming, just figured out the final solution that has no redundant iterations (I hope):
function main() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var range = sheet.getDataRange();
var [headers, ...data] = range.getValues();
var cols_to_clean = ['# of snacks requested','#2','#3','#4','#5','#6','#7','#8'];
// get all transactions (9 items for this data)
var all_transactions = data.map(x => x[headers.indexOf('Transaction #')]);
// get the short list of unique transaction (4 items for this data)
var uniq_transactions = [... new Set(all_transactions)];
for (let transaction of uniq_transactions) {
// get all indexes of rows with given transaction
var rows_to_clean = all_transactions.map((t, row) => t === transaction ? row : '')
.filter(String).slice(1);
// clean the rows
for (let r of rows_to_clean) {
cols_to_clean.forEach(c => data[r][headers.indexOf(c)] = '');
}
}
range.setValues([headers, ...data]);
}
I didn't remove my first update, I think this can be useful for educational purposes.
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
I have a large dataset pasted into Google Sheets. I want to compare two columns in this sheet.
The first column is filled with long strings, I only care about the first character in each cell in this column.
The second column is text-based. I want to paste data into a new sheet if the string in column A starts a number (not a letter) and the text in column B is "Early".
My challenge is using Javascript to get only the substring of 1 character for column A. The error is "TypeError: str.substring is not a function." I have also tried slice() as well to no effect. Is this not supported in Apps Script?
How can I isolate those first characters and then compare alongside the other column?
I also then need to push the rows that meet this criteria in both columns to a new tab.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('data');
var str = sheet.getRange('A:A').getValues();
var char = str.substring(0,2);
var stage = sheet.getRange('B:B');
var endColumn = sheet.getLastColumn();
var value_stage = (stage.getValues());
var value_char = (char.getValues());
var csh = ss.getSheetByName('new_sheet'); //destination sheet
var data = [];
var j =[];
for (i=0; i<value_char.length;i++) {
if
(
(
( value_char[i] == '0') ||
( value_char[i] == '1') ||
( value_char[i] == '2') ||
( value_char[i] == '3') || etc... )
&&
( value_stage[i] == 'Early')
)
data.push.apply(data,sheet.getRange(i+1,1,1,endColumn).getValues());
j.push(i);
}
}
csh.getRange(csh.getLastRow()+1,1,data.length,data[0].length).setValues(data);
SAMPLE DATA BELOW:
**ID** **Stage** **Name**
A texthere Early Bob
B abcdefgh Late Sally
52 texthere Early Jim
C thesdfas Late Allan
00 tsdfsdfd Late Susan
11 qqwerrww Early Ryan
Q tsdfsagd Early Sarah
98 fdsafads Early Evan
09 fdasfdsa Early Paul
10 abcdefgh New Megan
10 abcdefgh Early Cooper
NOTE: in the real dataset, columns A and B are actually K & L
And the ID column has those spaces between the early part of the string and the end. This is what I am trying to slice off
Try this:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh=ss.getSheetByName('data');
var svs=sh.getRange(1,2,sheet.getLastRow(),2).getValues();
var csh=ss.getSheetByName('new_sheet');
var data=[];
var j=[];
svs.forEach(function(r,i){
if((r[0].slice(0,1)=='1' || r[0].slice(0,1)=='2') && r[1]=='Early') {
//do what you want in here
}
});
I have customer purchase data which is exported from SQL database. The output format in Google sheets looks like this:
ID name address phone product name
1 Bob 2030 random road 6265609245 A
1 B
2 Peter 5453 golden drive A
2 D
3 Jason 1 dna way C
4 James sfo drive A
4 B
4 C
I'd like to know if I could autofill the blank cells with the upper cell values if they have the same ID. To illustrate this please see the desired output format below:
ID name address phone product name
1 Bob 2030 random road 6265609245 A
1 Bob 2030 random road 6265609245 B
2 Peter 5453 golden drive A
2 Peter 5453 golden drive D
3 Jason 1 dna way C
4 James sfo drive A
4 James sfo drive B
4 James sfo drive C
Any help will be greatly appreciated!
I assume that you want to complete the row based on the ID. If my assumption is correct, you can use this Apps Script code:
function so62048323() {
var data = SpreadsheetApp.getActive().getDataRange().getValues();
var nameDictionary = new Object();
var addressDictionary = new Object();
var phoneDictionary = new Object();
for (var r = 1; r < data.length; r++) {
if (data[r][0] != '') {
if (data[r][1] != '') {
nameDictionary[data[r][0]] = data[r][1];
}
if (data[r][2] != '') {
addressDictionary[data[r][0]] = data[r][2];
}
if (data[r][3] != '') {
phoneDictionary[data[r][0]] = data[r][3];
}
}
}
for (var r = 1; r < data.length; r++) {
if (data[r][1] == '') {
data[r][1] = nameDictionary[data[r][0]];
}
if (data[r][2] == '') {
data[r][2] = addressDictionary[data[r][0]]
}
if (data[r][3] == '') {
data[r][3] = phoneDictionary[data[r][0]];
}
}
SpreadsheetApp.getActive().getDataRange().setValues(data)
}
The code will initiate reading all your table with SpreadsheetApp.getActive(), Spreadsheet.getDataRange() and Range.getValues(). After that it will create three objects as dictionaries.
After that the code will iterate each row and record in the dictionaries each value (name, phone and address) associated with the ID. Afterwards, the code will repeat the operation but writing down the values. Finally, all the data is loaded in the sheet with Range.setValues(). For example, if we have this spreadsheet:
And we use the script, we will end up with this:
Please, notice how some ID doesn't have any phones; these are left empty. I understand that you may be using a very big database, so I optimized the code so it will run the fastest possible without taking into account the data size. Also, this code will work even if the rows aren't ordered. Don't hesitate to ask me any question if you have doubts.