Related
Please excuse my limited knowledge in advance.
In Google Sheets, I have a budget column (K) that sums multiple inputs and displays the most recent totals.
I am trying to create a script that will copy select values from column K and set those values into column I upon execution.
Both column K and I have sum totals in various rows throughout so a simple copy and pasting of the entire column would overwrite those functions. This will be a repeatable action to set new values once they are calculated into this column so I plan to assign it to a button.
I'm using User Tanaike's wonderful RangeListApp to help reference the multiple ranges I'm referencing.
The first half of the code seems to be working:
function UpdateEFC() {
var rangeList = ['K4:K6', 'K9:K14', 'K17:K18', 'K21:K24', 'K27', 'K30:K38', 'K41:K49', 'K52:K53', 'K56:K60', 'K63:K67', 'K70:K72', 'K75:K76', 'K79:K85', 'K88:K92', 'K95:K106', 'K109:K114', 'K117:K126', 'K129:K133', 'K136:K147', 'K150:K153', 'K156:K163', 'K166:K167', 'K170:K174', 'K177', 'K180:K182', 'K185', 'K188:K190', 'K193', 'K196:K205', 'K208:K214', 'K217'];
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var r = RangeListApp.getSpreadsheet(spreadsheet).getRangeList(rangeList).getDisplayValues();
Again, each range listed (i.e. "k4:k6") are sum functions in each cell. When I log this script it returns the calculated value in each cell. Here is an example from the log:
[{range='CR Template'!K4:K6, values=[[ $ 28,000.00 ], [ $ 25,000.00 ], [ $ 27,500.00 ]]},
When I attempt to set these values into column I, however, I run into issues.
I have tried the following:
var destinationList = ["I4:I6", "I9:I14", "I17:I18", "I21:I24", "I27", "I30:I38", "I41:I49", "I52:I53", "I56:I60", "I63:I67", "I70:I72", "I75:I76", "I79:I85", "I88:I92", "I95:I106", "I109:I114", "I117:I126", "I129:I133", "I136:I147", "I150:I153", "I156:I163", "I166:I167", "I170:I174", "I177", "I180:I182", "I185", "I188:I190", "I193", "I196:I205", "I208:I214", "I217"];
var values = rangeList;
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
RangeListApp.getSpreadsheet(spreadsheet).getRangeList(destinationList).setValues(values);
This script returns the cell ranges into column as written in the script. i.e. I4 = K4:K6, I5 = K4:K6, I6 = K4:K6 but does not return the displayed numerical summed value.
I have also tried a version where
var values = r;
My thinking was to take the output of displayed values and set those values into the new range list, however this only returned:
[object Object]
into each cell.
I know I have something wrong here in how to define or recall the values so any guidance here would be appreciated.
Many thanks in advance.
I believe your goal is as follows.
You want to copy the values from ['K4:K6', 'K9:K14', 'K17:K18',,,] to ["I4:I6", "I9:I14", "I17:I18",,,].
Issue and workaround:
In the current stage, unfortunately, my RangeListApp cannot copy the values with the ranges. So, in this case, as a current workaround, I would like to propose using a script with Sheets API. The sample script is as follows.
Sample script:
Before you use this script, please enable Sheets API at Advanced Google services.
function myFunction() {
var sheetName = "Sheet1"; // Please set your sheet name.
// These ranges are from your script.
var srcRangeList = ['K4:K6', 'K9:K14', 'K17:K18', 'K21:K24', 'K27', 'K30:K38', 'K41:K49', 'K52:K53', 'K56:K60', 'K63:K67', 'K70:K72', 'K75:K76', 'K79:K85', 'K88:K92', 'K95:K106', 'K109:K114', 'K117:K126', 'K129:K133', 'K136:K147', 'K150:K153', 'K156:K163', 'K166:K167', 'K170:K174', 'K177', 'K180:K182', 'K185', 'K188:K190', 'K193', 'K196:K205', 'K208:K214', 'K217'];
var dstRangeList = ["I4:I6", "I9:I14", "I17:I18", "I21:I24", "I27", "I30:I38", "I41:I49", "I52:I53", "I56:I60", "I63:I67", "I70:I72", "I75:I76", "I79:I85", "I88:I92", "I95:I106", "I109:I114", "I117:I126", "I129:I133", "I136:I147", "I150:I153", "I156:I163", "I166:I167", "I170:I174", "I177", "I180:I182", "I185", "I188:I190", "I193", "I196:I205", "I208:I214", "I217"];
var ssId = SpreadsheetApp.getActiveSpreadsheet().getId();
var values = Sheets.Spreadsheets.Values.batchGet(ssId, { ranges: srcRangeList.map(e => `'${sheetName}'!${e}`), valueRenderOption: "FORMATTED_VALUE" }).valueRanges;
var data = values.map(({ values }, i) => ({ range: `'${sheetName}'!${dstRangeList[i]}`, values }));
Sheets.Spreadsheets.Values.batchUpdate({ data, valueInputOption: "USER_ENTERED" }, ssId);
}
When this script is run, the values of srcRangeList is copied to dstRangeList.
Note:
I would like to consider updating RangeListApp.
References:
Method: spreadsheets.values.batchGet
Method: spreadsheets.values.batchUpdate
I'm trying to use Google Forms to collect some information on my clients. The application here is a clinical setting, so the form asks what treatments they would like in the form of a checkbox option. I would then like to automate the sum total of the treatment costs using the forms output.
The issue I'm having is that Google Forms outputs a list of strings in a single cell for this response. I'll add more detail below, but I don't know how to split the string into individual values, lookup that value in a separate column, get the cost, and display only the sum in a separate cell.
I've made a minimal working example in the form of a GSheet, you can find it here.
In that master sheet, you'll find three other sheets; Costs, Form Responses, and Overview.
The Costs sheet is static and only contains a list of items and their costs. This sheet will change on occasion (price updates, removal/addition of items)
The Form Responses sheet will contain the raw output from a Google Form. The column of note here is the Choose Things from the List column, which contains a list of responses.
The Overview sheet will house some redundant info, but it's meant to be a cleaned-up sheet with information. You'll notice a Cost of Things ($) column. In this column, I would like the total sum of all items the response list from the Form Responses sheet.
I can do this in Python easy. I would do it something like this:
costs = {'a': 1, 'b': 2, 'c': 3}
input_items = ['a', 'b', 'c']
x = []
for item in input_items:
x.append(costs[item])
total_sum = sum(x)
How would I do this with Google Sheets? I want to
split a list embedded in a cell
check each list item for its cost in a separate sheet
sum the costs of each item
Please let me know if I need to clarify, I'm not quite sure how to pose the problem using Google Sheets language.
==============
EDIT: Sorry, I just updated the GSheet permissions. It should be viewable to everyone now.
In your situation, how about the following sample script?
Sample script:
function myFunction() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet1 = ss.getSheetByName("Costs");
const sheet2 = ss.getSheetByName("Forms Responses");
const sheet3 = ss.getSheetByName("Overview");
// 1. Retrieve values from "Costs" sheet and create an object.
const values1 = sheet1.getRange("A2:B" + sheet1.getLastRow()).getValues();
const obj1 = values1.reduce((o, [a, b]) => (o[a.trim()] = b, o), {});
// 2. Retrieve values from "Forms Responses" sheet and create an object.
const values2 = sheet2.getRange("A2:C" + sheet2.getLastRow()).getValues();
const obj2 = values2.reduce((o, [a, b, c]) => (o[a.trim() + b.trim()] = c.split(",").reduce((n, e) => (n += obj1[e.trim()] || 0, n), 0), o), {});
// 3. Retrieve values from "Overview" sheet and create an array.
const values3 = sheet3.getRange("A2:B" + sheet3.getLastRow()).getValues();
const res = values3.map(([a, b]) => [obj2[a.trim() + b.trim()] || null]);
// 4. Put array to "Overview" sheet.
sheet3.getRange(2, 3, res.length, 1).setValues(res);
}
In this sample script, the following flow is run.
Retrieve values from "Costs" sheet and create an object.
Retrieve values from "Forms Responses" sheet and create an object.
Retrieve values from "Overview" sheet and create an array.
Put array to "Overview" sheet.
In this case, the result values are put to the column "C".
Note:
This sample script is for your sample Spreadsheet. So, when you change the Spreadsheet, the script might not be able to be used. Please be careful about this.
References:
getValues()
setValues(values)
reduce()
map()
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()
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])
I have a Google Sheet where I collect responses and another tab where I see a report for each record.
I want to add manually a number in the cell Writing Points (as in the image below) and click Update to update the cell of that specific record in the responses tab.
I managed to get the row number depending on the student the formula is:
MATCH($D$3,Responses!D:D, 0)
And the column is always BG of the repsonses tab.
How can I achieve this through Google script? I have an idea but I don't know how to do it.
My try:
row = MATCH($D$3,Responses!D:D, 0)
SpreadsheetApp.getActiveSheet().getRange('BG'+row).setValue(newwritingpoints);
I don't know how to conver the match formula into Google script syntax and how to copy and paste the value from that cell to the responses tab.
Google Sheet link:
https://docs.google.com/spreadsheets/d/1dE6UVABhVp7WqEFdC0ptBK2ne6wEA6hCf0Wi6PxcMWE/edit#gid=2032966397
I believe your goal as follows.
When Update() of Google Apps Script is run by the button, you want to retrieve the cells "D3" and "H24" in the sheet Report.
You want to search the value of "D3" from the column "D" in the sheet Responses.
When the value of "D3" and the values of column "D" in the sheet Responses are the same, you want to put the value of "H24" to the same row of the column "BG" in the sheet Responses.
For this, how about this answer? The flow of this sample script is as follows.
Retrieve the values from the cells "D3" and "H24" in the sheet Report.
Retrieve the values from the cells "D2:D" in the sheet Responses.
Create the range list from the retrieved values.
Put the value using the range list.
Sample script:
function Update() {
var spreadsheet = SpreadsheetApp.getActive();
// 1. Retrieve the values from the cells "D3" and "H24" in the sheet `Report`.
var reportSheet = spreadsheet.getSheetByName("Report");
var searchText = reportSheet.getRange("D3").getValue();
var writingPoints = reportSheet.getRange("H24").getValue();
// 2. Retrieve the values from the cells "D2:D" in the sheet `Responses`.
var responsesSheet = spreadsheet.getSheetByName("Responses");
var values = responsesSheet.getRange("D2:D" + responsesSheet.getLastRow()).getValues();
// 3. Create the range list from the retrieved values.
var ranges = values.reduce((ar, [d], i) => {
if (d == searchText) ar.push("BG" + (i + 2));
return ar;
}, []);
// 4. Put the value using the range list.
responsesSheet.getRangeList(ranges).setValue(writingPoints);
}
In this script, it also supposes the case that the value of "D3" might find at several rows in the sheet Responses.
References:
reduce()
Class RangeList