Create new row for each not empty column - google-apps-script

I'm trying to convert an Excel macro to Google Apps Script. I would like to create a new row on a specific sheet for each not empty column in Google Spreadsheets.
My Inputsheet looks like the following:
ID | Inrellevant Column | Givenmoney | Takenmoney | Othermoney
1 | Data1 | 100 | 200 | 300
2 | Data2 | 400 | | 500
I want to create a new row in another sheet for each not empty cell, so the desired Outputsheet would be:
ID | Inrellevant Column | Moneycode | Amount
1 | Data1 | Givenmoney | 100
1 | Data1 | Takenmoney | 200
1 | Data1 | Othermoney | 300
2 | Data2 | Givenmoney | 400
2 | Data2 | Othermoney | 500
I tried the following:
Outputsheet.getRange('A2').offset(0, 0, Inputsheet.length).setValues(Inputsheet);
However I can't see to create a loop to create new rows for each not empty column.

Hoi Fred, assuming you want the output to appear from the top left cell onwards in the sheet 'Outputsheet', try this code:
function myFunction() {
var ss = SpreadsheetApp.getActive(),
source = ss.getSheetByName('Inputsheet'),
target = ss.getSheetByName('Outputsheet'),
arr = [
["ID", "Header 2nd col", "Moneycode", "Amount"]
],
data = source.getDataRange().getValues(),
headers = data[0];
data.splice(1)
.forEach(function (r) {
r.forEach(function (c, i) {
if (!isNaN(parseFloat(c)) && isFinite(c) && i > 1) {
arr.push([r[0], r[1], headers[i], c])
}
})
})
target.getRange(1, 1, arr.length, arr[0].length).setValues(arr);
}
See this example sheet where you can run the above script from the menu 'My Menu'....

Related

Script to search for multiple unread labels in Gmail threads at once

I have the following structure of labels
+------------+------------+---------------+
| label | sub-label | sub-sub-label |
+------------+------------+---------------+
| 01-fruit | | |
| | 01-apples | |
| | | green |
| | | red |
| | 02-oranges | |
| | | red |
| | | orange |
| 02-veggies | | |
| | 01-peppers | |
| | | green |
| | | red |
+------------+------------+---------------+
The script in use is:
function mail2Sheets() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('newRec'); //get the sheet
var freshLabel = GmailApp.getUserLabelByName("00-fresh"); // in the end, add this label
const query = "label:unread" + " label:01-fruit";
var foundThreads = GmailApp.search(query);
var newReceipts = [];
for (var i = 0; i < foundThreads.length; i++) {
+++++++ SOME CODE HERE +++++++
}
}
if(!foundThreads.length) return; // if there are no unread ones, do nothing.
sheet.getRange(SpreadsheetApp.getActiveSheet().getLastRow()+1,2,newReceipts.length,newReceipts[0].length).setValues(newReceipts); //write to sheet
GmailApp.markThreadsRead(foundThreads); // mark "foundThreads" as read
freshLabel.addToThreads(foundThreads); // add label "00-fresh" to "foundThreads"
GmailApp.refreshThreads(foundThreads); // refresh "foundThreads" for changes to show
}
I can successfully search for a single label like: const query = "label:unread" + " label:01-fruit";
Also.
Although I have GmailApp.refreshThreads(foundThreads); the Execution never completes.
Instead it shows Status Running
TO RECAP
How can I make the query search at the same time for multiple labels like
"label:unread" + " label:00-fruit/01-apples/red"
AND
"label:unread" + " label:02-veggies/01-peppers/red"
Also. How can the Status Running issue be fixed?
" " which is a space is used as AND operator.
OR and {} can be used as OR operator.
Using above operators, your goal can be achieved.
When you want to search the mails with label:unread and label:00-fruit/01-apples/red, please use the search query as follows.
label:unread label:00-fruit/01-apples/red
When you want to search the mails with label:unread and label:00-fruit/01-apples/red or label:02-veggies/01-peppers/red, please use the search query as follows.
label:unread (label:00-fruit/01-apples/red OR label:02-veggies/01-peppers/red)
or
label:unread {label:00-fruit/01-apples/red label:02-veggies/01-peppers/red}
Reference:
Search operators you can use with Gmail

Add blank rows or header between unique values in Sort

I am pulling data from tabs in a sheet and putting them into a single tab using "compilation.sort(6,true);"
It pulls the data in, but it would be fantastic if there was a way to add rows during the sort, instead of afterwards. Is there a way to insert rows, or even a header, between unique values of the sort?
I have tried: "compilation.sort(6,true).insertRowAfter(i,1);" without success.
What I Have:
Row 1 | Row 2 | Row 3
--------- ------- -------
Pasta | Sauce | 3
Pasta | Sauce | 4
Pasta | Sauce | 5
Pizza | Sauce | 10
Pizza | Sauce | 11
Pizza | Sauce | 12
Spaghetti | Sauce | 9
What I am looking for:
Row 1 | Row 2 | Row 3
--------- ------- -------
Pasta | Sauce | 3
Pasta | Sauce | 4
Pasta | Sauce | 5
Pizza | Sauce | 8
Pizza | Sauce | 11
Pizza | Sauce | 12
Spaghetti | Sauce | 9
For the active sheet you can use
function runSample2() {
/**
* #type {conditionCallback}
*/
var cb;
cb = function(row, i, values) {
// Returns true if it's not the first row,
// the first cell of a row does not equal one of previous row
// and there is no an empty row before
return (
values[i - 1] &&
values[i - 1][0] !== row[0] &&
values[i - 1].join('') !== ''
);
};
var sheet = SpreadsheetApp.getActiveSheet();
insertRowBeforeByCondition_(sheet, cb);
}
function insertRowBeforeByCondition_(sheet, condition) {
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
var i = values.length;
while (i-- > 0)
if (condition(values[i], i, values)) sheet.insertRowBefore(i + 1);
return sheet;
}
Based on the snippet.

How to pass a cell reference to an Apps Script custom function?

Assuming that:
A1 = 3
B1 = customFunc(A1) // will be 3
In my custom function:
function customFunc(v) {
return v;
}
v will be 3. But I want access the cell object A1.
The following is transcribed from the comment below.
Input:
+---+---+
| | A |
+---+---+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
+---+---+
I want to copy A1:A4 to B1:C2 using a custom function.
Desired result:
+---+---+---+---+
| | A | B | C |
+---+---+---+---+
| 1 | 1 | 1 | 2 |
| 2 | 2 | 3 | 4 |
| 3 | 3 | | |
| 4 | 4 | | |
+---+---+---+---+
To achieve the desired result of splitting an input list into multiple rows, you can try the following approach.
function customFunc(value) {
if (!Array.isArray(value)) {
return value;
}
// Filter input that is more than a single column or single row.
if (value.length > 1 && value[0].length > 1) {
throw "Must provide a single value, column or row as input";
}
var result;
if (value.length == 1) {
// Extract single row from 2D array.
result = value[0];
} else {
// Extract single column from 2D array.
result = value.map(function (x) {
return x[0];
});
}
// Return the extracted list split in half between two rows.
return [
result.slice(0, Math.round(result.length/2)),
result.slice(Math.round(result.length/2))
];
}
Note that it doesn't require working with cell references. It purely deals with manipulating the input 2D array and returning a transformed 2D array.
Using the function produces the following results:
A1:A4 is hardcoded, B1 contains =customFunc(A1:A4)
+---+---+---+---+
| | A | B | C |
+---+---+---+---+
| 1 | a | a | b |
| 2 | b | c | d |
| 3 | c | | |
| 4 | d | | |
+---+---+---+---+
A1:D4 is hardcoded, A2 contains =customFunc(A1:D4)
+---+---+---+---+---+
| | A | B | C | D |
+---+---+---+---+---+
| 1 | a | b | c | d |
| 2 | a | b | | |
| 3 | c | d | | |
+---+---+---+---+---+
A1:B2 is hardcoded, A3 contains =customFunc(A1:B2), the error message is "Must provide a single value, column or row as input"
+---+---+---+---------+
| | A | B | C |
+---+---+---+---------+
| 1 | a | c | #ERROR! |
| 2 | b | d | |
+---+---+---+---------+
This approach can be built upon to perform more complicated transformations by processing more arguments (i.e. number of rows to split into, number of items per row, split into rows instead of columns, etc.) or perhaps analyzing the values themselves.
A quick example of performing arbitrary transformations by creating a function that takes a function as an argument.
This approach has the following limitations though:
you can't specify a function in a cell formula, so you'd need to create wrapper functions to call from cell formulas
this performs a uniform transformation across all of the cell values
The function:
/**
* #param {Object|Object[][]} value The cell value(s).
* #param {function=} opt_transform An optional function to used to transform the values.
* #returns {Object|Object[][]} The transformed values.
*/
function customFunc(value, opt_transform) {
transform = opt_transform || function(x) { return x; };
if (!Array.isArray(value)) {
return transform(value);
}
// Filter input that is more than a single column or single row.
if (value.length > 1 && value[0].length > 1) {
throw "Must provide a single value, column or row as input";
}
var result;
if (value.length == 1) {
// Extract single row from 2D array.
result = value[0].map(transform);
} else {
// Extract single column from 2D array.
result = value.map(function (x) {
return transform(x[0]);
});
}
// Return the extracted list split in half between two rows.
return [
result.slice(0, Math.round(result.length/2)),
result.slice(Math.round(result.length/2))
];
}
And a quick test:
function test_customFunc() {
// Single cell.
Logger.log(customFunc(2, function(x) { return x * 2; }));
// Row of values.
Logger.log(customFunc([[1, 2, 3 ,4]], function(x) { return x * 2; }));
// Column of values.
Logger.log(customFunc([[1], [2], [3], [4]], function(x) { return x * 2; }));
}
Which logs the following output:
[18-06-25 10:46:50:160 PDT] 4.0
[18-06-25 10:46:50:161 PDT] [[2.0, 4.0], [6.0, 8.0]]
[18-06-25 10:46:50:161 PDT] [[2.0, 4.0], [6.0, 8.0]]

Google script Multiple criteria in same row and validation of value in previous row

I'm starting with a script that someone here graciously helped with and need to build onto it and do not know where to start. Here is the current script:
function yourFunction(){
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Sheet1');
var rg=sh.getDataRange();//columns are fruit,status and then cost.
var vA=rg.getValues();
for(var i=1;i<vA.length;i++){
if(vA[i][0].toString()=='Apple' && vA[i][1].toString()=='Ripe' && vA[i][2].toString=='Large' && vA[i][4].toString=''){
vA[i][4]=5.5;
}
}
rg.setValues(vA);//This writes all of the data at one time.
}
What I would like to add to this is a second set of criteria that looks at another column value = Lot Number(Column D). Assuming that the current Lot Number is the same as the previous row's and where all the above match, each additional rows will be a set value 3. But if the value of the Lot Number before the current row is not the same, then the value is 5. In what I've read, there may need to be some looping condition in this so the calculations don't keep going on and on. Any help here would be much appreciated. Thanks!
Here is a link to a basic format of the spreadsheet Test Script
Here's an updated function that demonstrates the general approach for what I believe you're describing.
var COLUMNS = {
FRUIT: 0,
STATUS: 1,
SIZE: 2,
LOT_NUMBERr: 3,
COST: 4,
NEW_VALUE: 5,
}
function updateValues() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Sheet3');
var range = sheet.getDataRange();
var values = range.getValues();
var previousLotNumber = -1;
for(var i = 1; i < values.length; i++){
if (values[i][COLUMNS.FRUIT] == 'Apple'
&& values[i][COLUMNS.STATUS] == 'Ripe') {
values[i][COLUMNS.COST] = 5.5;
}
if (previousLotNumber == values[i][COLUMNS.LOT_NUMBER]) {
values[i][COLUMNS.NEW_VALUE] = 3;
} else {
values[i][COLUMNS.NEW_VALUE] = 5;
}
previousLotNumber = values[i][COLUMNS.LOT_NUMBER];
}
range.setValues(values);
}
After running this function, this sheet looks like the following:
+--------+-----------+--------+------------+------+-----------+
| Fruit | Status | Size | Lot Number | Cost | New Value |
+--------+-----------+--------+------------+------+-----------+
| Apple | Ripe | Large | 101 | 5.5 | 5 |
| Apple | Ripe | Medium | 101 | 5.5 | 3 |
| Apple | Ripe | Large | 103 | 5.5 | 5 |
| Apple | Not Ready | Large | 102 | | 5 |
| Apple | Not Ready | Medium | 101 | | 5 |
| Banana | Ripe | Large | 201 | | 5 |
| Orange | Ripe | Large | 301 | | 5 |
| Orange | Not Ready | Medium | 301 | | 3 |
| Pear | Ripe | Large | 401 | | 5 |
+--------+-----------+--------+------------+------+-----------+
A few notes:
use descriptive variable names
use a set of constants to provide descriptive names for the columns
for your ask, all you need to do is use a variable to store the previous rows lot number, no additional looping complexity required

Google Sheets script - find matching tuples and copy data between sheets

I have 2 sheets in the same spreadsheet, call them sheet1 and sheet2. In each sheet, every row describes some hardware component and its properties. The point of sheet2 is to eventually replace the outdated sheet1.
Simple example, (real sheets are hundreds of lines long):
sheet1:
componentId | prop1 | prop2 | prop3 | isvalid
---------------------------------------------
1 | x1 | y1 | z1 | yes
2 | x1 | y2 | z3 | yes
3 | x2 | y1 | z1 | yes
sheet2:
componentId | quantity | prop1 | prop2 | prop3 | prop4 | isvalid
----------------------------------------------------------------
15 | 4 | x1 | y1 | z1 | w1 | TBD
23 | 25 | x3 | y3 | z2 | w1 | TBD
33 | 3 | x1 | y2 | z3 | w2 | TBD
The final column "isValid" in sheet1 has been manually populated. What I would like to do is write a script that iterates through sheet1, producing a tuple of the property values, and then looks for matching property value tuples in sheet2. If there is a match, I would like to copy the "isValid" field from sheet1 to the "isValid" field in sheet2.
What I have so far is the following, but I am experiencing a error "The coordinates or dimensions of the range are invalid" - see comment in code below showing where error is. And, the entire thing feels really hacky. Was hoping someone could maybe point me in a better direction? Thanks in advance.
function arraysEqual(a, b) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length != b.length) return false;
for (var i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}
function copySheetBasedOnRowTuples(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName('sheet 1 name');
var sheet2 = ss.getSheetByName('sheet 2 name');
s2data = sheet2.getDataRange().getValues()
s1data = sheet1.getDataRange().getValues()
for( i in s1data ){
sheet1Tuple = [ s1data[i][1], s1data[i][2], s1data[i][3] ]
// Now go through sheet2 looking for this tuple,
// and if we find it, copy the data in sheet1 column 4
// to sheet2 column 6 for the rows that matched (i and j)
for ( j in s2data){
sheet2Tuple = [ s2data[j][2], s2data[j][3], s2data[j][4] ]
if ( arraysEqual(sheet1Tuple, sheet2Tuple) ){
// ERROR HAPPENS HERE
sheet2.getRange(j, 6).setValue( sheet1.getRange( i, 4 ).getValue() )
}
}
}
}
The reason of error is the start number between array and range. The index of array starts from 0. The row and column of getRange() start from 1. So how about this modification?
From :
sheet2.getRange(j, 6).setValue( sheet1.getRange( i, 4 ).getValue() )
To :
sheet2.getRange(j+1, 7).setValue( sheet1.getRange( i+1, 5 ).getValue() )
If this was not useful for you, please tell me. I would like to modify.