Concatenating merged cells and on a specific condition in apps script - google-apps-script

I have this table where I want to concatenate each row in it on specific columns
The concatenation should happen on columns Option Name, Type 1, Type 2and Existing measure/New measure
For Existing measure and New measure wherever there is an entry that value should be picked up for concatenation.
The output I want is an array as below
[Name 1-C type 1-Yearly-GB, Name 1-C type 2-Monthly-MB, Name 2-C type 3-Quarterly-GB, Name 2-C type 2-Daily-Bytes,Name 2-C type 4-Monthly- MB,Name 3-C type 1-Yearly-KB]
I could use an array formula and concatenate and store in column L and read that column via apps script. but I want to use apps script to get the array I desire.
Please help!

I believe your goal is as follows.
You want to convert your sample input Spreadsheet image to the array of The output I want is an array as below.
You want to put the created array in the column "L".
You want to achieve this using Google Apps Script.
In your situation, how about the following sample script?
Sample script:
function myFunction() {
const sheetName = "Sheet1"; // Please set your sheet name.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const range = sheet.getRange("C7:K" + sheet.getLastRow());
let temp = "";
const ar = range.getDisplayValues().map(([c, , , , , ...v]) => {
if (c) temp = c;
return [[c, ...v].join("") && [temp, ...v.filter(String)].join("-")];
});
console.log(ar); // You can see the created array in the log.
// If you want to retrieve the flattened array like your question, please use console.log(ar.flat());
range.offset(0, 9, ar.length, 1).setValues(ar);
}
When this script is run, the values are retrieved from columns "C" and "H" - "K", and an array is created including the string values concatenated columns "H" - "K". And, the created array is put to column "L".
Reference:
map()

Related

How would I convert a string from a single spreadsheet cell into a 2D array with GAS?

I need to generate a table in Google Docs from a set of value pairs in a single spreadsheet cell. The values in the cell (A1) are formatted like this:
["a1","a2"],["b1","b2"],["c1","c2"]
I've tried:
testData = testSheet.getRange(1,1,1, 1).getValues().toString().split(",");
Logger.log(testData)
This gives me:
[["a1","a2"],["b1","b2"],["c1","c2"]]
Which looks like a 2D array to me, but when I try:
testDoc.insertTable(testField, testData)
I get the error:
Exception: The parameters (number,number[]) don't match the method signature for DocumentApp.Body.insertTable.
When I define the array manually in the function with:
testData = [["a1","a2"],["b1","b2"],["c1","c2"]]
The table is inserted as expected.
I could format the data in the cell differently, but I need all the value pairs to be in a single cell.
Modification points:
When ["a1","a2"],["b1","b2"],["c1","c2"] is put in a cell "A1", and when the cell value is retrieved by testSheet.getRange(1,1,1, 1).getValues(), it is [["[\"a1\",\"a2\"],[\"b1\",\"b2\"],[\"c1\",\"c2\"]"]]. In this case, [\"a1\",\"a2\"],[\"b1\",\"b2\"],[\"c1\",\"c2\"] is the string type.
This can be confirmed by JSON.stringify.
By this, when the cell value is retrieved by testSheet.getRange(1, 1, 1, 1).getValues().toString().split(","), it becomes ["[\"a1\"","\"a2\"]","[\"b1\"","\"b2\"]","[\"c1\"","\"c2\"]"]. In this case, [\"a1\"","\"a2\"], [\"b1\"","\"b2\"] and [\"c1\"","\"c2\"] are the string values.
I thought that this is the reason for your issue.
In order to use the cell value of ["a1","a2"],["b1","b2"],["c1","c2"] as a 2 dimensional array, it is required to parse it.
When these points are reflected in your script, how about the following modification?
From:
testData = testSheet.getRange(1,1,1, 1).getValues().toString().split(",");
To:
testData = JSON.parse(`[${testSheet.getRange(1, 1, 1, 1).getValue()}]`); // In this case, I think that `getDisplayValue()` is used instead of `getValues`.
By this modification, the value of ["a1","a2"],["b1","b2"],["c1","c2"] can be used as 2 dimensional array of [["a1","a2"],["b1","b2"],["c1","c2"]].
Testing:
const value = '["a1","a2"],["b1","b2"],["c1","c2"]';
const testData = JSON.parse(`[${value}]`);
console.log(testData)
console.log(testData[0][0]) // a1
Reference:
JSON.parse()
I'd try this:
var testData = eval('[' + testSheet.getRange('A1').getDisplayValue() + ']');
Test code:
var s = '["a1","a2"],["b1","b2"],["c1","c2"]';
var testData = eval('[' + s + ']');
console.log(testData);

Google Sheets Apps Script, return random item from named range

I'm trying to create a custom function that I can give a name range as input and have it output a random item from the name range. I have multiple named ranges so it would be convenient to have one function that I could use for all of them. This is what I'm trying to replace =INDEX(named_range,RANDBETWEEN(1,COUNTA(named_range)),1)
This is what I've tried but it doesn't work:
function tfunction(n) {
var randomstuffs = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(n);
var randomstuff = randomstuffs[Math.floor(Math.random()*randomstuffs.length)];
Logger.log(randomstuff);
}
Thanks in advance
You can try this edited script:
function tfunction(n) {
//randomstuffs will only get all cell data that are not empty from a named range
var randomstuffs = [].concat.apply([], SpreadsheetApp.getActiveSpreadsheet().getRangeByName(n).getValues()).filter(String);
var randomstuff = randomstuffs[Math.floor(Math.random()*randomstuffs.length)];
return randomstuff;
}
Sample Result
Created a sample named range TestRange on Column A with 21 cells of data then tried the custom function =tfunction("TestRange") which returned a random cell value.
.getRangeByName() method returns a reference to a range, not the values in the range. You need to add .getValues() to it:
var randomstuffs = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(n).getValues();
Also keep in mind that .getValues() method returns a 2D array of values, indexed by row, then by column. So your var randomstuff declaration will need to be change to account for that, depending on how many rows and columns your range has.

Script to move email addresses from one sheet to another

I have problems figuring out how to script the following:
1. I have a sheet tab_A:
Col_A, Col_B
URL_1, Email_1
URL_2, Email_2
URL_3, Email_3
1. I have a sheet tab_B:
Col_A, ..., Col_F
URL_3, empty
URL_1, empty
URL_2, empty
"..." means several columns with data.
I need a script that takes row for row in tab_A, copy the email address, find the correct row in tab_B (the same URL) and paste the email in Col F.
Any help is appreciated.
I believe your goal as follows.
You want to copy the values of the column "B" of "tab_A" to the column "F" of "tab_B" by checking the column "A" of "tab_A" and "tab_B" on the Spreadsheet.
For example, when the row 1 has URL_1, Email_1 on "tab_A" and the row 2 has URL_1 on "tab_B", you want to copy the value of Email_1 to the column "F" of "tab_B" with the same row.
In order to achieve your goal, I would like to propose the following flow.
Retrieve values from "tab_A" and create an object for searching the value of the column "A".
Create an array for putting values to "tab_B".
Put the values to "tab_B".
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet and run myFunction. By this, the values are put to "tab_B". Before you use this, please set the sheet names for your actual situation.
function myFunction() {
// 1. Retrieve values from "tab_A" and create an object for searching the value of the column "A".
const ss = SpreadsheetApp.getActiveSpreadsheet();
const tabA = ss.getSheetByName("tab_A");
const sourceValues = tabA.getDataRange().getValues();
const obj = sourceValues.reduce((o, [a, b]) => Object.assign(o, {[a]: b}), {});
// 2. Create an array for putting values to "tab_B".
const tabB = ss.getSheetByName("tab_B");
const outputValues = tabB.getDataRange().getValues().map(r => [obj[r[0]] || ""]);
// 3. Put the values to "tab_B".
tabB.getRange(1, 6, outputValues.length, 1).setValues(outputValues);
}
Note:
When you want to use above script as the custom function, you can also use the following script. In this case, please put a custom formula of =SAMPLE(tab_A!A1:B, tab_B!A1:A) to the cell "F1" of "tab_B".
function SAMPLE(valuesOfTabA, valuesOfTabB) {
const obj = valuesOfTabA.reduce((o, [a, b]) => Object.assign(o, {[a]: b}), {});
return valuesOfTabB.map(r => [obj[r[0]] || ""]);
}
References:
getValues()
setValues(values)
Custom Functions in Google Sheets
reduce()
map()

How do you apply a JOIN and FILTER formula to an entire column in Google Sheets using Google Script?

I'm very new to Google Scripts so any assistance is greatly appreciated.
I'm stuck on how to apply my formula which uses both JOIN and FILTER to an entire column in Google Sheets.
My formula is: =JOIN(", ",FILTER(N:N,B:B=R2))
I need this formula to be added to each cell in Column S (except for the header cell) but with 'R2' changing per row, so in row 3 it's 'R3', row 4 it's 'R4' etc.
This formula works in Google sheets itself but as I have sheet that is auto replaced by a new updated version daily I need to set a google script to run at certain time which I can set up via triggers to add this formula to my designated column.
I've tried a few scripts I've found online but none have been successful.
If you want to solve this using only formulas:
Since your formula is always in the format:
=JOIN(", ",FILTER(N:N,B:B=R<ROW NUMBER>))
and you want to apply it to a very large number of rows, you can use INDIRECT and ROW to achieve a dynamic formula. This answer has a good example on how to use this.
Using formulas you don't risk running into time limits with Apps Script
In practical terms, if you have your data on column A, you can write =ARRAYFORMULA(CONCAT("R",ROW(A2:A))) to get something like this:
Your final formula should look like this:
=JOIN(", ",FILTER($N:$N,B:B=INDIRECT(CONCAT("R",ROW($R2)))))
Also, you can drag it down to other cells like any other formula!
Set the formulas through Apps Script:
You can use setFormulas(formulas) to set a group of formulas to all the cells in a range. formulas, in this case, refers to a 2-dimensional array, the outer array representing the different rows, and each inner array representing the different columns in each specific row. You should build this 2D array with the different formulas, while taking into account that the row index from R should be different for each single formula.
You could do something like this:
function settingFormulas() {
var sheet = SpreadsheetApp.getActive().getSheetByName("Sheet1");
var firstRow = 2;
var column = 19; // Column S index
var range = sheet.getRange(firstRow, column, sheet.getLastRow() - firstRow + 1);
var formulas = range.getValues().map((row, index) => {
let rowIndex = index + firstRow;
return ["=JOIN(\", \",FILTER(N:N,B:B=R" + rowIndex + "))"];
});
range.setFormulas(formulas);
}
In this function, the optional index parameter from the method map is used to keep track of the row index, and adding it to the formula.
In this function, the sheet name is used to identify which sheet the function has to set the formulas to (in this case, the name's Sheet1). Here I'm assuming that once the sheet is replaced by a newer one, the sheet name remains the same.
Execute this daily:
Once you have this function, you just have to install the time-driven trigger to execute this function daily, either manually, following these steps, or programmatically, by running this function once:
function creatingTrigger() {
ScriptApp.newTrigger("settingFormulas")
.timeBased()
.everyDays(1)
.create();
}
Reference:
setFormulas(formulas)
getRange(row, column, numRows)
Installable Triggers
Instead of the workaround hacks I implemented a simple joinMatching(matches, values, texts, [sep]) function in Google Apps Script.
In your case it would be just =joinMatching(R1:R, B1:B, N1:N, ", ").
Source:
// Google Apps Script to join texts in a range where values in second range equal to the provided match value
// Solves the need for `arrayformula(join(',', filter()))`, which does not work in Google Sheets
// Instead you can pass a range of match values and get a range of joined texts back
const identity = data => data
const onRange = (data, fn, args, combine = identity) =>
Array.isArray(data)
? combine(data.map(value => onRange(value, fn, args)))
: fn(data, ...(args || []))
const _joinMatching = (match, values, texts, sep = '\n') => {
const columns = texts[0]?.length
if (!columns) return ''
const row = i => Math.floor(i / columns)
const col = i => i % columns
const value = i => values[row(i)][col(i)]
return (
// JSON.stringify(match) +
texts
.flat()
// .map((t, i) => `[${row(i)}:${col(i)}] ${t} (${JSON.stringify(value(i))})`)
.filter((_, i) => value(i) === match)
.join(sep)
)
}
const joinMatching = (matches, values, texts, sep) =>
onRange(matches, _joinMatching, [values, texts, sep])

Fetch text string from specific column and select range of that string

I have shared below link of my sheet.
I tried every possible script to accomplish below task in the end in frustration i wipe out my whole script.
I would like to match text from column A and return or getValue of corresponding B column.
So I can use that getValue from its corresponding B column for further arithmetic operations.
Thank you.
sheet link - https://docs.google.com/spreadsheets/d/1SwYYacz9A9s6ZXrL44KtRN7gqnwXkhu4cDILdUA1iJQ/edit?usp=sharing
Basic steps:
Retrieve your data range
Form an 1D array out of your column A entries, e.g. with map()
Check either the search string is contained in the array - and if yes retrieve its position - e.g. with indexOf()
Retrieve the value with the respective row index in column B
Sample:
function myFunction() {
var matchText = "C";
var values = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getDataRange().getValues();
var columnA =values.map(function(e){return e[0]});
var row = columnA.indexOf(matchText);
if (row >= 0){
var Bvalue = values[row][1];
Logger.log(Bvalue);
}
}
I encourage you to take some time to study Apps Script, so you cannot only understand this code and adapt to your needs, but also write your own scripts.