Google Scripts Concatenate Cells in different columns - google-apps-script

I have the following scenario where I want to concatenate cells in different columns but the quantity of columns differs from case to case.
example1: In cell A2 there is the value of 70286 & and the cell B2 there is the value of playmobil.
The end result in F2 should be +70286 +playmobil
example2: In cell A4 there is the value of 70666, in the cell B4 there is the value of lego and in the cell C4 there is the value of ninjago.
The end result in F2 should be +70666 +lego +ninjago

Solutions:
Google Sheet Formula:
You can use TEXTJOIN and if you want a plus sign in front of the string you can use CONCAT to add it.
Use this formula in column F:
=CONCAT("+",textjoin(" +", 1, A2:E2))
If you don't know how many columns you want to use, then you can select the full row:
=CONCAT("+",textjoin(" +", 1, A2:2))
Google Apps Script:
We get the data of the second row as a 2D array using getValues().
Then we apply flat() to convert it to 1D.
The next step is to remove the empty cells by using filter().
Finally, we use reduce() to get the final string as the sum of all strings.
The logic can be easily adjusted. You can do this logic for multiple cells etc.
Code snippet:
function myFunction() {
const sh = SpreadsheetApp.getActive().getSheetByName('Sheet1');
const row = 2;
const data=sh.getRange(row,1,1,sh.getMaxColumns()).
getValues().
flat().
filter(c=>c!='').
reduce( (a, b) => `${a} +${b}`);
const result = `+${data}`;
sh.getRange('F1').setValue(result);
Logger.log(result);
}

Related

How to combine these two App Script formulas?

If both columns E and K have value in them → then run the function in the column G.
As a regular Sheets formula, it looks like this (it works):
=if(not(isblank({$E12,$K12})),$K12*$E12," ")
I need App Script that does the same.
Why do I need a Script instead of regular formula? Because Arrayformula doesn't allow me to write in the same column, I will need this same script for other similarly made formulas.
1)This one is for checking if cells are blank (E12 and K12):
var isCellBlank = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Copy of Produkti').getRange().isBlank();
if(!isCellBlank) {
iSheet.getRange().setValues();
}
2)This one is for the function I want to run:
function FillFormulas() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Copy of Produkti');
var lastRow = spreadsheet.getLastRow();
spreadsheet.getRange("G4").setFormula("=if(not(isblank({$E12,$K12})),$K12*$E12," ")");
var fillDownRange = spreadsheet.getRange(4,7,(lastRow-1)); spreadsheet.getRange("G4").copyTo(fillDownRange);
}
Function onEdit(e) I have in a different file.
Can you help me fill in the first part of the Script (isCellBlank - get.Range i guess)?
And how to combine these two scripts, so that it does the same as regular sheet formula, just for the whole column (like Arrayformula)?
If you could add a hidden column before G you could follow this approach I've explained in this other post:
You could sort it out with hidden columns, and values that expand to the next column in which you'd like to have your result or input your value.
Instead a formula: =B3/B4 you'd have ={",",B3/B4}
So, if you manually insert a value in the right column, the formula in left column won't expand. As an example I've created a very dummy calculator of sides and hypothenuse. In B1 (column B in red would be hidden) I have:
={"","Res: "&(C3^2-C2^2)^(1/2)}
And so on in the next rows. In this picture I've inserted manual values in C1 and C2, so C3 has a result:
[]
1
And another example in another row:
You can test it out here and the extrapolate it to your formulas,
(I've erased the part with "Res. " so the result is an actual number, it was just for easier view in my examples):
https://docs.google.com/spreadsheets/d/1ZaEEGWtLU-LXBmg-xy80Ps_biuYhb8WxZBs0g1tGE00/edit?usp=drivesdk

Disable Google Sheets formula

Is there a way to disable all code located in every cell of a sheet by using a function in Google App Script?
For example in A1 I have:
=query(d10Thur0721!B2:I1275, "select B, C, D, E where D > 0", 0)
In H2 I have:
=if($G$9="", "", "men:"&roundup(sum(countif($G$9:$G$551, "*m*")-countif(G9:G600, "*mef*"))/countif($G$9:$G$551, "**")*100))&"%"
In theory I'd like to just replace the = with '= thus disabling all the code however I'm not sure how to get the script grab the actual code in the cell. If I reference the cell A1 Google Apps Script will return whatever value is in the cell and not the actual code residing in A1.
To get the cell formula use SpreadsheetApp.Range.getFormula(), to get the formulas of all cells in a range use SpreadsheetApp.Range.getFormulas().
To set the cell formula use SpreadsheetApp.Range.setFormula(formula) where formula is a string, to get the formulas of all cells in a range use SpreadsheetApp.Range.setFormulas(formulas) where formulas is string[][] (an 2D Array, outer Array elements are Arrays of strings).
To set the cell values use SpreadsheetApp.Range.setValue(value) where value is a string, to set the values of all cells in a range use SpreadsheetApp.Range.setValues(values) where values is string[][] (an 2D Array, outer Array elements are Arrays of strings).
Below is a "creative" script. It disable the formulas in the active range keeping the values of cells not having formulas.
Instead of using getFormula and getFormulas / setFormula and setFormulas, it only use getValues and getFormulas and instead of setFormula/ setFormulas it uses setValues(values)
/**
* Disable formulas in the active range.
* https://stackoverflow.com/a/74329523/1595451
*
* #author Rubén https://stackoverflow.com/users/1595451/rubén
*/
function disableFormulas(){
const range = SpreadsheetApp.getActiveRange();
const formulas = range.getFormulas();
const values = range.getValues();
range.setValues(formulas
.map((rowFormulas, row) => rowFormulas
.map((formula, column) => formula
? formula.replace(/^=/,`'=`)
: values[row][column];
)
)
)
}
Reference
https://developers.google.com/apps-script/reference/spreadsheet/range

Score calculation, considering negative marking

I'm trying to find if I can ask a script to calculate a grade where for each good answer it gives +1, and for each wrong answer it substracts -0,25. The calculation will be made in column C, as shown in the screenshot. In the example, the correct score is 6,25 instead of 7.
BTW, the score calculation refers to the key answers on the row 2.
I believe your goal as follows.
You want to calculate for the values of column "C" by checking the background colors of each cell using Google Apps Script.
When the background color of cell is #3ace9c (green), you want to add 1.
When the background color of cell is #ff9900 (orange), you want to reduce 0.25.
You want to calculate for the values of column "C" by comparing the columns "D" to "M" at the row 2 (the referring answer) and each row (after row 3) using Google Apps Script.
When the columns "D" to "M" at the row 2 are the same with each row, you want to add 1.
When the columns "D" to "M" at the row 2 are NOT the same with each row, you want to reduce 0.25.
For this, how about this answer?
Pattern 1:
In this pattern, the background colors are used. The flow of this sample script is as follows.
Prepare an object including the color codes for "good" and "wrong" answers.
Retrieve the background colors from the range of "D3:M" in the sheet.
Calculate the values using the object and the background colors.
Put the values to the column "C" in the sheet.
Sample script:
function myFunction() {
const sheetName = "Sheet1"; // Please set the sheet name.
// 1. Prepare an object including the color codes for "good" and "wrong" answers.
const obj = {"#3ace9c": 1, "#ff9900": -0.25}; // Please set the color codes, if you change the color.
// 2. Retrieve the background colors from the range of "D3:M" in the sheet.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const backgrounds = sheet.getRange("D3:M" + sheet.getLastRow()).getBackgrounds();
// 3. Calculate the values using the object and the background colors.
const result = backgrounds.map(r => [r.reduce((c, e) => c += obj[e], 0)]);
// 4. Put the values to the column "C" in the sheet.
sheet.getRange(3, 3, result.length, 1).setValues(result);
}
Pattern 2:
In this pattern, the referring answer of the row 2 is used. The flow of this sample script is as follows.
Retrieve values from the range of "A2:M" in the sheet.
Retrieve the referring answer.
Calculate the values of each values by comparing the referring answer, and create an array including the result values.
Put the values to the column "C" in the sheet.
Sample script:
function myFunction() {
const sheetName = "Sheet1"; // Please set the sheet name.
// 1. Retrieve values from the range of "A2:M" in the sheet.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const values = sheet.getRange("A2:M" + sheet.getLastRow()).getValues();
// 2. Retrieve the referring answer.
const answers = values.shift();
answers.splice(0, 3);
// 3. Calculate the values of each values by comparing the referring answer, and create an array including the result values.
const result = values.map(([,,c,...dm]) => [dm.reduce((c, e, i) => e == answers[i] ? c + 1 : c - 0.25, 0)]);
// 4. Put the values to the column "C" in the sheet.
sheet.getRange(3, 3, result.length, 1).setValues(result);
}
References:
map()
reduce()
getBackgrounds()
getValues()
setValues()

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])

compare two colums in different spreadsheets in google script

i want to compare tow different columns in two different spreadsheets.
My first spreadsheet is named "testtabelle" and the other is named "test" the name of the sheets are both "Tabellenblatt1"
I want to compare column A # testtabelle with column A # test.
If the string are equal, i need the value from colum B # test and copy it into column b # testtabelle of the same row, where my strings matched.
I think i need two loops for every column and a if statement to compare the values.
I'll be glad if someone can help me!
You can use the SpreadsheetApp class to open multiple sheets. Look up openById or openByUrl, either should work. You can then make two spreadsheet objects, get the values of column A and B of each, iterate through to compare, and copy the value of column B if the values of column A match.
One thing to note is you should use getValue and setValue rather than copyTo as I don't think the latter works across separate spreadsheets.
You should end up with something like this:
// gets spreadsheet A and the range of data
ssA = SpreadsheetApp.openById('ID of spreadsheet A');
sheetA = ssA.getSheetByName('name of sheet in ssA');
dataA = sheetA.getRange('A:B').getValues();
// gets spreadsheet B and the range of data
ssB = SpreadsheetApp.openById('ID of spreadsheet B');
sheetB = ssB.getSheetByName('name of sheet in ssB');
dataB = sheetB.getRange('A:B').getValues();
// loops through column A of spreadsheet A & B and compares
for(var i = 0; i > sheetA.getLastRow(); i++){
// checks to see if ith value in 2nd row is the same
if dataA[1][i] == dataB[1][i]{
var value = sheetA.getRange(i+1, 2).getValue();
// used i+1 because index of range is 1, while index of the data array is 0
sheetB.getRange(i+1, 2).setValue(value);
} // end if
} // end i
There's also a similar question answered here.