I have a large workbook that pulls in weekly data (columns) for hundreds of metrics on several tabs. It pulls this data in via SUMIFS formulas, and on most tabs there are several rows that contain ratios/rates calculated from these SUMIFS formulas.
Here is a toy example.
For each sheet, I would like to paste values only for formulas that are based on 'SUMIFS', and leave the other calculations. I was able to select a range and loop cell-by-cell to accomplish this, but it takes a long time due to the size of the workbook. Is there a way to do this at once in a batch-fashion? Basically, I want to copy and paste-values only if a certain condition exists.
Solution:
Updated - Slight improvement to make it faster by Iamblichus.
I am not quite sure what you have tried so far, but this one works relatively fast for a couple of sheets:
function myFunction() {
let ss = SpreadsheetApp.getActiveSpreadsheet();
let sheets = ['Sheet1','Sheet2'].map(sh=>ss.getSheetByName(sh));
sheets.forEach(sh=>{
let range = sh.getDataRange();
let formula = range.getFormulasR1C1();
let values = range.getValues();
formula.forEach( (fr,fx) =>
{fr.forEach((fc,fy)=>{
let outR=sh.getRange(fx+1,fy+1);
if (fc.toUpperCase().includes('SUMIFS')) outR.setValue(values[fx][fy]);
})})});
}
Please adjust ['Sheet1','Sheet2','Sheet3'] to your needs.
Explanation:
I am iterating through each sheet and I am checking whether the cells contain SUMIFS formula or not. If they do, I overwrite them with their value, otherwise I keep their formula.
Related
I am (VERY) new to Apps Script and JS generally. I am trying to write a script that will automatically tally the difference between student entry time and start time of a course to deliver total minutes missed.
I have been able to get a function working that can do this for a single cell value, but am having trouble iterating it across a range. Doubtless this is due to a fundamental misunderstanding I have about the for loop I am using, but I am not sure where to look for more detailed information.
Any and all advice is appreciated. Please keep in mind my extreme "beginner status".
I have tried declaring a blank variable and adding multiple results of previously written single-cell functions to that total, but it is returning 0 regardless of given information.
I am including all three of the functions below, the idea is that each will do one part of the overall task.
function LATENESS (entry,start) {
return (entry-start)/60000
}
function MISSEDMINUTES(studenttime,starttime) {
const time = studenttime;
const begin = starttime;
if (time=="Present") {
return 0
} else if (time=="Absent") {
return 90
} else {
return LATENESS(time,begin)
}
}
function TOTALMISSED(range,begintime) {
var total = 0
for (let i = 0; i < range.length; i++) {
total = total + MISSEDMINUTES(i,begintime)
}
}```
If you slightly tweak your layout to have the 'missing minutes' column immediately adjacent to the column of names, you can have a single formula which will calculate the missing minutes for any number of students over any number of days:
Name
*
2/6
2/7
2/8
2/9
John Smith
-
Present
Present
Absent
10:06
Lucy Jenkins
-
Absent
Absent
Absent
Absent
Darren Polter
-
Present
Present
Present
10:01
With 'Name' present in A1, add the following to cell B1 (where I've marked an asterisk):
={"mins missed";
byrow(map(
C2:index(C2:ZZZ,counta(A2:A),counta(C1:1)),
lambda(x,switch(x,"Present",0,"Absent",90,,0,1440*(x-timevalue("10:00"))))),
lambda(row,sum(row)))}
We are MAPping a minute value onto each entry in the table (where 'Present'=0, 'Absent'=90 & a time entry = the number of minutes difference between then and 10am), then summing BYROW.
Updated
Based on the example, you could probably have a formula like the below one to conduct your summation:
=Sum(ARRAYFORMULA(if(B2:E2="Absent",90,if(isnumber(B2:E2),(B2:E2-$K$1)*60*24,0))))
Note that k1 has the start time of 10:00. Same sample sheet has working example.
Original Answer
I'm pretty sure you could do what you want with regular sheets formulas. Here'a sample sheet that shows how to get the difference in two times in minutes and seconds... Along with accounting for absent.
Here's the formula used that will update with new entries.
=Filter({if(B2:B="Absent",90*60,Round((C2:C-B2:B)*3600*24,0)),if(B2:B="Absent",90,Round((C2:C-B2:B)*3600*24/60,1))},(B2:B<>""))
This example might not solve all your issues, but from what I'm seeing, there's no need to be using an app script. If this doesn't cover it, post some sample data using Mark down table.
Script lab shows examples of how to get a range using rows, columns and cell values here:
https://learn.microsoft.com/en-us/office/dev/add-ins/excel/excel-add-ins-ranges-advanced
However, all the values in examples were hardcoded. I could not find on any page any example of how to use variables in the get range? Maybe its a simple javascript thing but I am totally new to it. Can anyone share an example of how to do say?
sheet.getRange("4:9") using variables for number 4 and number 9?
And if you do know that answer for above, can you also share, how to do this using cell references in the below example?
Assume that I can find the values of rows and column names and set them in some variables. How would I use it in below code replacing the values for G1, A1 and E1?
sheet.getRange("G1").copyFrom("A1:E1");
Thanking you in advance for your help!
P.S: I already tried searching on stack overflow with keywords for "script lab range variables" but found no answers. Hence asking here.
Excel.run(function (context) {
var sheet = context.workbook.worksheets.getItem("Sample");
// Group the larger, main level. Note that the outline controls
// will be on row 10, meaning 4-9 will collapse and expand.
sheet.getRange("4:9").group(Excel.GroupOption.byRows);
// Group the smaller, sublevels. Note that the outline controls
// will be on rows 6 and 9, meaning 4-5 and 7-8 will collapse and expand.
sheet.getRange("4:5").group(Excel.GroupOption.byRows);
sheet.getRange("7:8").group(Excel.GroupOption.byRows);
// Group the larger, main level. Note that the outline controls
// will be on column R, meaning C-Q will collapse and expand.
sheet.getRange("C:Q").group(Excel.GroupOption.byColumns);
// Group the smaller, sublevels. Note that the outline controls
// will be on columns G, L, and R, meaning C-F, H-K, and M-P will collapse and expand.
sheet.getRange("C:F").group(Excel.GroupOption.byColumns);
sheet.getRange("H:K").group(Excel.GroupOption.byColumns);
sheet.getRange("M:P").group(Excel.GroupOption.byColumns);
return context.sync();
}).catch(errorHandlerFunction);
So, I got an answer on this here: https://github.com/OfficeDev/office-js-docs-pr/issues/1699
Thanks to AlexJerabek (you can find his id on the above github link).
In summary:
The getRange method takes in a string representing a cell range in the worksheet, such as "A1:D4", "A:D" for just the columns, or "1:4" for just the rows. As long as your variables map to Excel rows or columns, you can use string concatenation to build the argument. You may need to use range.columnIndex, range.rowIndex, and range.getCell to translate to and from zero-based numbers into the letter-based columns.
Based on this answer, I tried below (and it worked):
const sheet = context.workbook.worksheets.getActiveWorksheet();
var firstrow = 1
var lastRow = 6
var range = sheet.getRange(firstrow + ":" + lastRow)
So I've built a script which grabs (public) data from Instagram, dumps it in a table and then I construct a dashboard based on it.
It looks like this:
Now, I've built a formula that calculates the daily new followers if there is a value filled in:
=if(B3<>"",B3-B2,"")
However, in my script I use sheet.appendRow to (job-based) auto-populate the spreadsheet with new data every night at 12. The issue I have is that when I drag my formula down, say 20 lines to not have to go in everyday to drag down the formula), my sheet.appendRow will paste it below those 20 lines (which essentially, are visually blank).
I've been looking around on Google (and stack) to find a function that can do the same as my formula, and will calculate the new daily followers every time at night (when the job runs), but my knowledge of Scripting is to limited to find a solution that works. So basically: the value from the current row in column B - the value from the row above in column B = New Daily Followers.
I prefer to solve this with a Google Script, as I'm looking to do additional calculations on other columns in a similar fashion, and can then just adapt the script to calculate these new KPI's.
Can anyone assist me in creating this?
Apend in C1:
={"Header" ; " " ; FILTER(B3:B-B2:B,A2:A<>"")}
and delete all other formulas from column C.
Notes:
The formula will create the filter and it's no need to drag down, will adjust automatically.
assumed all cells from A2:A have go blanks inside the table
"Header" ; " " to leave C2 blank
I am looking to be pulling in some data into a Google Sheet from an external source at 1am daily. The amount of products imported will vary.
I then wish to sort the data by price after the data has finished uploading, this will be around 1.05am.
I see it is possible to run functions for Google Sheets on a timed basis.
What script would I need to sort the data by price, so the cheapest items is row 2 after running a script every morning.
To do this manually in Google Sheets I would do - *Highlight rows 2 to , Data, Sort Range, Sort by column I, A - Z
How would this translate into a function? the timed function look easy enough to do as a trigger (although a set time doesn;t seem possible only an hour range)
Here is the shared sheet
https://docs.google.com/spreadsheets/d/1hRW92xesCZzrTRU8DzrdJ_XtrQoJbbNhA3nVT452
apE/edit?usp=sharing
SOLUTION - Thanks to Ed & Cooper
First, freeze the first row manually. (Search for freeze) https://support.google.com/docs/answer/54813?hl=en
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
// Sorts the sheet by the column I - the ninth across - , in ascending order
sheet.sort(9);
}
Set this function to run one hour after you data is available:
function sortPrice() {
var ss=SpreadsheetApp.openById("1XHOlmI6LA4jvnh1YXossBrbrnIOdyRmvO2warz1vl9s")
var s=ss.getSheetByName("4cat").activate()
var lr =s.getLastRow()
var lc= s.getLastColumn()
var rng=s.getRange(2, 1, lr, lc)//get range to sort
var sort= rng.sort({column: 9, ascending: true}) //sort on column I
}
I have a Google Spreadsheet with 13 sheets. Each sheet has the same formatting and layout but the data varies. The ranges I need from each sheet are F7:G13 and cell F2. Each row within the F7:G13 range should be paired with Cell F2 and placed into 3 adjacent columns in another workbook (we'll call it Workbook2). So if there is a data point in column F there will be a data point in the adjacent G cell. As long as there is data in the G:F range then the contents of cell F2 needs to be copied over too.
Some potential problems...
The number of rows in the F:G range will vary from 0 to 7 on any given sheet.
Workbook2 has some auto-populated cells (range F1:J14) which will do some calculations based on information copied over. I mention this because unless there is a way to use appendRow on a specific cell range (A:C) it will copy the data starting in row 15 which I do not want.
I think i've included everything but I will gladly clarify anything. Thanks in advance for any help.