autofill cell values based on upper cell values in google sheets - google-apps-script

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.

Related

Split value of cell and replicate the whole row

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

Loop that finds duplicates, and deletes values after 1st instance of duplicate from a range of cells

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.

Substring comparison across columns within Google Apps Script

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
}
});

How to sum up the values in each column and ranking each column name by sum of values in google sheet

I have a google sheet with values that is getting populated
A B C D E F G H
Top scorers Date Player l Player 2 Player 3 Player 4
13 Jan 2019 1 1 1
20 Jan 2019 2 1 1
the idea is: each match day I will enter the date of the match and number of goals that each player scored, if new player score I will just put his name in new column and number of goal on that date. If any player not score that day, I will just leave that cell blank.
Then I want to populate first column "Top scorers" with ranking of Player scored. Expected result will look like this:
A B C D E F G H
Top scorers Date Player l Player 2 Player 3 Player 4
Player 1: 3 13 Jan 2019 1 1 1
Player 2: 2 20 Jan 2019 2 1 1
Player 3: 1
Player 4: 1
It will automatically updated with new data input. How could I make this? I have a look at Pivot Table but looks like it is hard to archive this result.
Sample sheet.
According to the description of what you're trying to accomplish and the Google Sheet shared.
You need to break your problem down into several subtasks:
Selecting all the ranges you'll later need (Players, Dates, Range where you'll write your scores, range where your top scorers will be displayed)
Adding up each player's goals from all the games to get their total score
Sorting the players according to their total score
Create strings to write into your topscorer column
Write these strings into the topscorer column
My solution is pretty verbose but it seems to work. Please don't hesitate to ask if you have any questions or if clarifications are needed.
function testMe() {
var ID = ''; // ID of your Document
var name = ''; // Name of your sheet
var sourceSheet = SpreadsheetApp.openById(ID); // Selects your Source Spreadsheet by its id
var ssheet = sourceSheet.getSheetByName(name); // Selects your Source Sheet by its name
var scoreRange = ssheet.getRange(2, 3, (sourceSheet.getLastRow() -1), (sourceSheet.getLastColumn() -2)); // Selects the range in which you will enter your scores
var dateRange = ssheet.getRange(2,2,(sourceSheet.getLastRow() -1)); // Selects the range for which player names in row 1
var playerRange = ssheet.getRange(1,3,1,(sourceSheet.getLastColumn() -2)); // selects the range for which dates were entered in column
var topScorerRange = ssheet.getRange(2,1,scoreRange.getNumColumns()); // selects the range where your topscorer output will end up
var numberOfPlayers = playerRange.getNumColumns(); // Gets the number of players you've already entered in row 1
var numberOfGames = playerRange.getNumRows(); // Gets the number of games whose dates you've already entered in Column B
function sortAndUpdateTopScorers() {
var array = scoreRange.getValues();
var totalPlayers = scoreRange.getNumColumns();
var totalGames = scoreRange.getNumRows();
var playerScores = [];
// iterate through the scoreRange and count up each players total score
for (var i = 0; i < totalPlayers; i++) {
var currentPlayer = 0;
for (var j = 0; j < totalGames; j++) {
currentPlayer += array[j][i];
}
playerScores.push([currentPlayer]);
}
// Combine the names of the players and their total score in order to create the strings for your topscorers column
for (var v = 0; v < numberOfPlayers; v++) {
playerScores[v].push(playerRange.getValues()[0][v] + ": " + playerScores[v]);
};
// Sort those strings according to their score
playerScores.sort(function(a,b) {
return b[0]-a[0]
});
// Remove the score value so only the string remains in the array
for (var x = 0; x < playerScores.length; x++) {
playerScores[x].shift();
}
// Write the content of the array into your topscorers column
topScorerRange.setValues(playerScores);
};
sortAndUpdateTopScorers();
};

How to ignore empty cells and change column order when copying data using Google Apps Script

I'm trying to write a script to paste rows of cells from one sheet to another while ignoring rows with blank cells in Column A. The script would also swap the order of Columns B and C. Here is what I am trying to do:
INPUT Sheet....................................OUTPUT Sheet
A1= ID B1= Last Name C1 = MI A1= ID B1 = MI C1= Last Name
A2= 1 B2= Stewart C2= M................A2= 1 B2= M C2= Stewart
A3= B3= Smith C3= R................A3= 4 B3= V C3= Holland
A4= 4 B4= Holland C4= V................A4= 3 B4= B C4= Young
A5= 3 B5= Young C5= B
Here is what I have:
function removeEmptyRows() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Input");
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Output");
var data = sheet.getRange("A2:D").getValues();
var newData = new Array();
for(i in data){
var row = data[i];
var empty = false;
for(i in data){
if(row[0] == ""){
empty = true;
}
}
if(!empty){
newData.push(row);
}
sheets.getRange(2, 1, newData.length, newData[0].length).setValues(newData);
};}
I have not been able to figure out how to incorporate changing the order of columns.
I was not able to apply previous posts to this problem, although this one came closest.
I would really appreciate any help you can provide. Thank you!