I have just started trying out the script editor of the Google Sheets.
Here is what I am trying to do:
I have a few "headers" in Sheet A. I will name them Header A, B, and C.
1 | Header A
2 | text 1
3 | text 2
4 | (empty row)
5 | Header B
6 | text 1
7 | text 2
8 | (empty row)
9 | Header C
10 | text 1
11 | text 2
Sheet B
1 | text 1 | date | Header A
2 | text 2 | date | Header B
3 | text 3 | date | Header B
4 | text 4 | date | Header C
5 | text 5 | date | Header A
When I update my data on Sheet B, it will be automatically be updated on Sheet A based on my custom attributes in Sheet B. I need it to be able to update the data on Sheet B onto Sheet A's empty row after the specific Headers.
Currently I am stuck at getting the next empty row in Sheet A. I am able to get the row that my Headers are in but I couldn't find any help online finding the next empty row after each specific Headers.
I could not find any classes that is provided. All I can see is getLastRow which is not what I want. I wonder can this be done?
Below is my current code:
function getScheduleStatus(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var name = ss.getSheetByName("Production Schedule");
var rng = name.getDataRange();
var last_row = rng.getLastRow();
var data = rng.getValues();
var str_upcoming = "upcoming"
var rows = [];
for (var i=0; i < data.length; i++) {
if (data[i][0].toLowerCase() == str_upcoming) {
Logger.log("true");
rows.push(i);
}
}
var row = Number(rows)+Number(rng.getRow());
Logger.log(row);
}
Currently I am able to find the Header as well as getting the row number.
Hope you guys can understand.
Take a look at Faster way to find the first empty row; the answers there can be adapted to your situation.
The fastest solution there was Don Kirby's, and since you have a single column of data, it is ideal. We just need to change it to start searching from a particular headerRow!
/**
* Search column A starting at the given row, to find
* the first following empty row. Uses spreadsheet row numbers.
* From: https://stackoverflow.com/a/38194740/1677912
*
* #param {Number} headerRow Row number to start search at.
*
* #returns {Number} Target empty row.
*/
function getFirstEmptyRowAfter(headerRow) {
var spr = SpreadsheetApp.getActiveSpreadsheet();
var column = spr.getRange('A:A');
var values = column.getValues(); // get all data in one call
var ct = headerRow; // Start at row after headerRow (0-adjusted)
while ( values[ct][0] != "" ) {
ct++;
}
return (ct + 1); // (1-adjusted)
}
Related
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 need to do a JSON Array using 3 or 4 colums but I can't fill the data using the "while" condition.
I have some Google Sheet Data which can have 3 or 4 columns, this data must be inside a JSON Array, for example:
|---------------------|------------------|---------------------|------------------|
| variable | condition | value | and/or |
|---------------------|------------------|---------------------|------------------|
| CPC | > | 100 | |
|---------------------|------------------|---------------------|------------------|
In this first example the 4th column is empty because we only have one row of data, so, the result must be:
["CPC",">",100]
if we have two rows (or more) of data, this should look like this:
|---------------------|------------------|---------------------|------------------|
| variable | condition | value | and/or |
|---------------------|------------------|---------------------|------------------|
| CPC | > | 100 | and |
|---------------------|------------------|---------------------|------------------|
| s_volume | < | 950 | and |
|---------------------|------------------|---------------------|------------------|
| s_volume | > | 100 | |
|---------------------|------------------|---------------------|------------------|
as you can see, the last row in the last column is empty, and the result should look like this:
["CPC",">",100],
"and",
["s_volume","<",950],
"and",
["s_volume",">",100]
Finally, this JSON array must me assigned to a variable.
I have tried with this:
//Get values from cells
var fil1 = ss.getSheetByName("app").getRange(row,4,1,1).getValue();
var fil2 = ss.getSheetByName("app").getRange(row,5,1,1).getValue();
var fil3 = ss.getSheetByName("app").getRange(row,6,1,1).getValue();
var fil4 = ss.getSheetByName("app").getRange(row,7,1,1).getValue();
//Count how many rows with data exist
var coldat = ss.getSheetByName("app").getRange("D1:D").getValues();
var coldats = coldat.filter(String).length;
var jsonarr = [fil1, fil2, fil3]
//loop para aƱadir nuevos filtros
while (row < coldats) {
fil4,[fil1,fil2,fil3];
row++;
};
Apparently with my code the var "jsonarr" is filled only with the first row, ignoring any number of rows below.
I have tried with several tutorials and array methods to make this JSON Object, but I just can't get the correct answer. I really sorry for this newbie question, I'm not expert in coding, just a geek that do some work and is learning the very basics.
This answer is offered to assist the OP to bring together a range of issues identified in the OP's original code. I have left a number of Logger statements in the answer code that the OP can use to identify values at different stages of the code, assist with troubleshooting, and as an aid to skill development.
The results of the code is contained in the variable fildata. Based on the sample data, the value is:
["CPC",">",100],"and",["s_volume","<",950],"and",["s_volume",">",100]
How this is exported is up to the OP.
The OP is trying to create a data array (non-complying JSON) for data in a spreadsheet as input to another system.
The design of the OP's code meant that it retrieved only one row of data, and it could never build up the array as expected. The OP's code is the basis of the answer, but there are several key changes:
Moved coldat and coldats to the top of the code so that the coldats could be used in defining the data range.
Retrieved a single data range (getRange(2, 4, coldats, 4)) and used 'getValues()' only once.
Created a variable to hold the results of code
Looped through the data row-by-row
For each row, evaluated whether Column G ("and/or" column) contained a value. Depending on the result, included Column G in the results for that row.
Built up the results by row-by-row increment.
function so54394711() {
// setup spreadsheet
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName("app");
//Count how many rows with data exist
var coldat = sheet.getRange("D2:D").getValues();
var coldats = coldat.filter(String).length;
//Logger.log("DEBUG: Number of rows of data = "+coldats); //DEBUG
// establish the data range and values
var range = sheet.getRange(2, 4, coldats, 4);
//Logger.log("DEBUG: range = "+range.getA1Notation());//DEBUG
var values = range.getValues();
//Logger.log("DEBUG: values: "+ values);//DEBUG
// create a variable to hold the results
var fildata = "";
//loop through the data by row, build the results line-by-line
for (i = 0; i < coldats; i++) {
var filD = values[i][0];
var filE = values[i][1];
var filF = values[i][2];
var filG = values[i][3];
// test if column G contains a value
if (filG.length == 0) {
// array is empty
fildata += '[' + '"' + filD + '","' + filE + '",' + filF + ']';
//Logger.log("DEBUG: G=MT, fildata = "+fildata);//DEBUG
} else {
//array not empty
fildata += '[' + '"' + filD + '","' + filE + '",' + filF + ']' + ',"' + filG + '",';
//Logger.log("DEBUG: G<>MT, fildata = "+fildata);//DEBUG
}
// Logger.log("DEBUG: fildata = "+fildata);//DEBUG
}
}
I've created a order capture form that captures the
1. location (dropdown),
2. delivery date (date)
3. Products (small text)
I have then added a formula to generate a new order no. everytime a new response is added.
=arrayformula( if( len(A2:A), "Order " & text(row(A2:A) - row(A2) + 1, "022"), iferror(1/0) ) )
I need a new sheet to be created with the tab name with the new order no. and the columns transposed along with the response inputs against them.
For example response form
Column B | Column C | Column D |Column E |Column F
Order no. | Location | Delivery Date | Product 1 | Product 2
Order 122 | XYZ | 2/16/2017 | 35kg | 45kg
New sheet generated named "Order 122"
Order no. | Order 122
Location | XYZ
Delivery Date | 2/16/2017
Product 1 | 35kg
Product 2 | 45kg
and a notification to be sent to an email.
Is this possible with google spreadsheets, if so, please do let me know how it can be done. Thank you in advance.
I am using the following to create a new sheet named after the order no. but it doesnt seem to be working based on this article.
function subTheForm(){
Logger.log('submit ran');
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lastRow = sheet.getLastRow();
var colB_Data = sheet.getRange(lastRow, 2).getValue();
ss.insertSheet(colB_Data);
};
I get this error when I run the script
The sheet name cannot be empty. (line 9, file "Code")
Solved.
I used the following code similar as what I'd posted. For some reason I kept getting an error, cleared the responses, deleted and recreated the script and triggers and it worked.
function onSubmit(e){
Logger.log('submit ran');
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
//Get last row of data
var lastRow = sheet.getLastRow();
var colB_Data = sheet.getRange(lastRow, 2).getValue();
ss.insertSheet(colB_Data);
};
I've got a Google Spreadsheet which looks like this:
ID | winkelName | 26/05/2015 |
1 | Foo | |
2 | Bar | |
3 | Foo2 | |
4 | Bar2 | |
I'm trying to read and write an array of objects with the following structure:
history[i].storeName
history[i].checkIns
What I would like to do is to write all the number of checkins (with the same winkelName) on the same row under the column date (26/05/2015). But I'm still stuck what is the best way to achieve this?
This is my first attempt:
function writeHistory(history) {
var sheet = historySheet.getSheets()[0];
historySheet.setActiveSheet(sheet);
var nextRow = sheet.getLastRow(); // get next row
var rows = sheet.getDataRange().getValues();
var lastColumn = sheet.getLastColumn();
sheet.insertColumnAfter(lastColumn);
lastColumn++;
sheet.getRange(1, lastColumn).setValue(new Date());
var column = sheet.getLastColumn();
for(i in rows) {
if(i != 0) {
if(rows[i][1] == history[i].winkelName) {
sheet.getRange(i, column).setValue(history[i].checkIns); // this is not fully working
}
}
}
}
In a Google spreadsheet you can write a range of cells at once. To do so, create a multi dimensional array with one column and for each row another dimension and within each row a dimension for each cell you want to write.
Loop through your checkings, set the cell values in your array and write the values using sheet.getRange(1,column-index).setValue([your array]);
Also check: Google's best practices on dealing with range values
I have a sheet, say it's called raw sheet like this:
Column Index | A | B |
David | 1 | 10 |
Jerry | 5 | 15 |
David | 1 | 50 |
Jerry | 6 | 20 |
David | 8 | 20 |
There are only limited values in Column Index. Like in this case only "David" and "Jerry".
I want to create another sheet, say it's called summary sheet that can summarize some value by the Column Index value, like this:
Column Index Summary | f(A,B) |
David | some value |
Jerry | some value |
The f(A,B) can be any kind of function that take use of all the values in the first sheet. One example: to add every row's A*B to get a new number. In this case, it would be:
Column Index Summary | f(A,B) |
David | 220 | that is 1*10 + 1*50 + 8*20
Jerry | 195 | that is 5*15 + 6*20
What should I do?
Here's a Google Sheets custom function for you. It will operate on any arbitrary table of numerical data, aggregating the numbers by any arbitrary (but simple) algebraic expression. (Expression must be valid in Javascript, e.g. "A * B", "A + B / C", or even "Math.pow(A,B)".) There's no error checking, so it's not fool-proof.
Examples:
=summary('raw sheet'!A1:C6,"A*B") Yes, you can refer to different sheets.
=summary(A1:C6,"A*A + B")
=summary(A1:C6,"Math.pow(A,B)")
Custom Function
/**
* Performs given formula on each row in table, aggregating (summing)
* row results by the key value in first column.
*
* See: http://stackoverflow.com/questions/26925283/how-do-i-get-add-and-sum-by-column-index-value-in-google-spreasheet/26942156#26942156
*
* #param {range} table Input data table, including headers
* #param {string} formula Mathematical function to peform on each
* row in table, using header values as
* function parameters.
* #param {int} sortType (Optional, default 1) 0: do not sort, 1: sort ascending, -1: sort descending
* #param {int} sortColumn (Optional, default 1) Column to sort by.
*
* #return {range} Summary table of results
* #customfunction
*/
function summary(table,formula,sortType,sortColumn) {
sortType = (sortType == undefined) ? 1 : sortType;
sortColumn = (sortColumn == undefined) ? 1 : sortColumn;
// Sort comparison function for ordering summary table
// uses sortType & sortColumn
function colCompare(a,b)
{
var col = sortColumn - 1;
var order = sortType;
if (!order) return 1;
else
return ((a[col] < b[col]) ? -order : ((a[col] > b[col]) ? order : 0));
}
var headers = table[0];
// Start results with its header row
var summaryTable = [[headers[0],String(formula)]];
// evaluate formula, replacing variables (headers) with references to table
for (var h = 1; h < headers.length; h++) {
var re = new RegExp(headers[h],"g");
formula = formula.replace( re, " table[row]["+parseInt(h)+"] " );
}
// Aggregate data by summing formula for each row
var summary = {};
for (var row=1; row<table.length; row++) {
var key = table[row][0];
if (!(key in summary))
summary[key] = 0;
summary[key] += eval( formula );
}
// Append aggregated rows to results, and return
for (key in summary) {
summaryTable.push([key,summary[key]]);
}
// Sort the results
headers = summaryTable.splice(0, 1);
summaryTable.sort(colCompare).unshift(headers[0]);
return summaryTable;
}
EDIT: Nov 17 - added sort functionality
Assuming you have the names in Col A and the col A of the example is actually Col B,
in the summary sheet, try something like:
=ArrayFormula(query({'raw sheet'!A2:A,'raw sheet'!B2:B*'raw sheet'!C2:C}, "select Col1, sum(Col2) where Col1 <>'' group By Col1 label sum(Col2) 'TOTAL' "))