Getting script to move through to the next sheet(TAB) - loops

function tabGrab () {
let ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getActiveSheet();
let sheetName = ss.getSheetName();
let allSheets = ss.getSheets();
let sheetCount = ss.getNumSheets();
let thisSheet = ss.getActiveSheet().getIndex() -1;
let skipSheets = ['ESCM Schedule data','Location Data','Costco U Data','FOG Data','FOG Trap Repair Data','Unauth Waste Data','Weekly Hazardous Waste Data Dump',
'SPCC Data Dump','Stormwater BMP Data Dump','Drill Down','Feeder'];
allSheets.forEach(function(sheet){
Logger.log(sheet);
})
ss.getActiveSheet().getSheetName()
Logger.log(sheetName);
let i = thisSheet;
let notDone = true;
if (i == (sheetCount -1)) {
i = 0;
} else {
i++;
}
let nextName = allSheets[i].getName();
if (skipSheets.indexOf(nextName) === -1){
let nextSheet = allSheets[i];
ss.setActiveSheet(nextSheet);
notDone = false;
}
Logger.log(nextName);
I am working on a workbook that rotates through a set of dynamic values (they change daily). the data is a set of locations. (each location is assigned a unique number and name). I kept running into the error, "Could not create sheet name/it already exists".
So what I am ideally trying to do is when the dynamic values change it triggers the code to run. 1st the code will run through the workbook and assign the tab based on the location number(a numeric value) in "B2". Then run again this time assigning the locations name (text string) in "C2". Now I have data sheets that I am trying to exclude and I think that is working thus far.
Now the code runs and renames the sheet but only does one sheet then activates the next tab and goes no further. I need to creat a loop but so far I have failed.
function pullName() {
var spreadsheet = SpreadsheetApp.getActive();
let newName = spreadsheet.getRange('B2').getValues();
let finalName = spreadsheet.getRange('C2').getValues();
spreadsheet.getActiveSheet().setName(finalName);
}
function doForAllTabs(){
var spreadsheet = SpreadsheetApp.getActive();
var allSheets = spreadsheet.getSheets();
allSheets.forEach(function(sheet){
if (sheet.getSheetName() !== ['ESCM Schedule data','Location Data','Costco U Data','FOG Data','FOG Trap Repair Data','Unauth Waste Data','Weekly Hazardous Waste Data Dump',
'SPCC Data Dump','Stormwater BMP Data Dump','Drill Down','Feeder']){
sheet.activate();
pullName();
}
})
}

I went through your code and this is what I end up with:
function doForAllTabs() {
const skip = ['ESCM Schedule data', 'Location Data', 'Costco U Data', 'FOG Data', 'FOG Trap Repair Data', 'Unauth Waste Data', 'Weekly Hazardous Waste Data Dump', 'SPCC Data Dump', 'Stormwater BMP Data Dump', 'Drill Down', 'Feeder']
var ss = SpreadsheetApp.getActive();
ss.getSheets().filter(sh => !~skip.indexOf(sh.getName())).forEach(s => {
s.setName(s.getRange("c2").getValue());
})
}
You seem to be changing the name of all of the sheets to a name that is in the sheet. So I don't know if this will work or not because I don't know what's in the sheets
BTW this whole construct is not going to work:
if (sheet.getSheetName() !== ['ESCM Schedule data','Location Data','Costco U Data','FOG Data','FOG Trap Repair Data','Unauth Waste Data','Weekly Hazardous Waste Data Dump',
'SPCC Data Dump','Stormwater BMP Data Dump','Drill Down','Feeder']
I replaced it with my filter method. That may be why it wasn't working. If you provide a link to a spreadsheet, I'll go to another account and try it. You should be aware though that you will be exposing your email address to everyone. That's why I use a throwaway account.

Related

Google Apps Script For Loop Through Array Speed

I have a simple example of a function I am working on. I am trying to loop through a column of unique item IDs. If the item ID is found in the item ID column of another sheet, it pulls adjacent attributes from the data table, and assigns them in the same row. I have a function and it works, however, this is a base example. In reality I need to do this for 1000+ rows, and much larger data sets. It is currently taking 30-60 mins to run. I believe there is a much faster way to do this with arrays and using foreach and getvalues I'm just not sure how to get started. Any help would be greatly appreciated.
function example() {
var list = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("List");
var data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Data")
var listendRow = list.getLastRow();
var dataendRow = data.getLastRow();
var dataid = data.getDataRange().getValue();
for (var i = 2; i <= listendRow; i++) {
for (var j = 2; j <= dataendRow; j++){
var idnum = [list.getRange(i, 2,listendRow).getValue()];
var id = data.getRange(j, 3).getValue();
var name = data.getRange(j, 4).getValue();
var weight = data.getRange(j, 5).getValue();
if (idnum == id){
list.getRange(i, 3).setValue(name);
list.getRange(i, 4).setValue(weight);
}
}
}
}
Here is the link to the sheet:
https://docs.google.com/spreadsheets/d/1PPZKRXhiAAfFG1d-CU02MV_CSrqbdsCsyo_QADz5yiA/edit?usp=sharing
I believe your goal is as follows.
Your script works fine. Under this condition, you want to reduce the process cost of your script.
Modification points:
When I saw your sample Spreadsheet, V8 runtime is not used. Please enable V8 runtime. When V8 runtime is used, the process cost of the script can be reduced.
In your script, getValue and setValue are used in a loop. In this case, the process cost becomes high. Ref
SpreadsheetApp.getActiveSpreadsheet() can be declared one time.
In order to reduce the process cost of your script, how about the following modification?
Modified script:
Before you run this script, please enable V8 runtime.
function example2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var list = ss.getSheetByName("List");
var data = ss.getSheetByName("Data");
var obj = data.getRange("C2:E" + data.getLastRow()).getValues().reduce((o, [a, ...b]) => (o[a] = b, o), {});
var range = list.getRange("B2:B" + list.getLastRow());
var values = range.getValues().map(([b]) => obj[b] || [null, null]);
range.offset(0, 1, values.length, 2).setValues(values);
}
When this script is run, the values are retrieved from "Data" sheet and create an object for searching the ID. And, the values are retrieved from "List" sheet and an array for putting to the sheet is created. And also, the array is put to "List" sheet.
Note:
When you try to use this script without enabling V8 runtime, an error like Syntax error occurs. Please be careful about this.
This modified script is for your sample Spreadsheet. If your actual Spreadsheet is differnt structure from your provided sample one, this modified script might not be able to be used. Please be careful about this.
If you cannot use V8 runtime, please test the following modified script.
function example2b() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var list = ss.getSheetByName("List");
var data = ss.getSheetByName("Data");
var obj = data.getRange("C2:E" + data.getLastRow()).getValues()
.reduce(function (o, [a, b, c]) {
o[a] = [b, c];
return o
}, {});
var range = list.getRange("B2:B" + list.getLastRow());
var values = range.getValues().map(function ([b]) { return obj[b] || [null, null] });
range.offset(0, 1, values.length, 2).setValues(values);
}
References:
getValues()
setValues(values)
reduce()
map()

Google Apps Script Copy/Paste Filtered DataSet

I've been trying to write a script that simply takes a filtered data, copies it, and then pastes it into another sheet. Nothing I seem to do works. With the code below, which I found online, it should work, but I keep getting an error that states The number of rows in the range must be at least 1. However, I have data in the range A7:R500 and I'm only filtering out blanks and 'W'. Am I correct in this thinking?
function copyPaste(){
var sheet = SpreadsheetApp.getActiveSheet();
var values = sheet.getRange('A7:R500').getValues();
var hiddenValues = ['', 'W'];
values = values.filter(function(v){
return hiddenValues.indexOf(v[4]) == 'W';
});
sheet.getRange(1,21, values.length, 18).setValues(values);
}
Solution:
Since you are already using a filter Array, you can compare hiddenValues.indexOf(v[4]) to -1 to filter out blanks and "W".
Also, since your goal is to paste the results in a different sheet, you need to define both the source and the destination sheet. Create a sheet and plug its name into the new sheet name tag in the code below.
Sample Code:
function copyPaste() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getActiveSheet();
var sheet2 = ss.getSheetByName('<new sheet name>');
var values = sheet1.getRange('A7:R500').getValues();
var hiddenValues = ['', 'W'];
values = values.filter(function(v){
return hiddenValues.indexOf(v[4]) == -1;
});
sheet2.getRange(1,21, values.length, 18).setValues(values);
}
Reference:
indexOf()

Append date column to array imported from Gmail CSV File Google Apps Script

I am trying to automatically import a daily report which is emailed from a third party system (on which I can't edit the values sent) to a google sheet to create a report over time.
The code below works and imports the data correctly, however the CSV file does not include the date which makes reporting imposssible.
I can get the date from the email and again this is working however I am unable to append this to the data written to the sheet. Ideally the code would add an extra entry to the start of each element of the array with a date.
/**
* import CSV data directly from gmail attachments
*/
function importCSVFromGmail() {
var threads = GmailApp.search('subject:Agent Performance report for newer_than:1d');
// create empty array to hold data
var allData = [];
// get the threads that match this search
threads.forEach(function(thread) {
var messageCount = thread.getMessageCount();
// get the messages in a thread
var messages = thread.getMessages();
messages.forEach(function(message) {
var attachments = message.getAttachments();
attachments.forEach(function(attachment) {
// check attachment is CSV
if (attachment.getContentType() === 'text/csv') {
// extract the data
var csvData = Utilities.parseCsv(attachment.getDataAsString());
var date = Utilities.formatDate(new Date(message.getDate()), "GMT+1", "MM/dd/yyyy");
allData = allData.concat(csvData);
}
});
Logger.log(allData);
});
});
// paste into Google Sheet
var sheet = SpreadsheetApp.getActiveSheet();
var lastRow = sheet.getLastRow();
sheet.getRange(lastRow + 1, 1, allData.length, allData[0].length).setValues(allData);
}
Sample of CSV data
Agent,Tickets assigned,Tickets resolved,Tickets reopened,Tickets reassigned,First response SLA %,Resolution SLA %,FCR %,Private notes,Responses,Average 1st response time,Average response time,Average resolution time
Agent 1,1,2,,1,,100,100,,1,,,17:32:49
Agent 2,11,14,3,,88,86,67,3,15,06:09:35,04:04:20,19:44:18
Agent 3,18,25,7,,100,80,68,6,14,01:28:29,02:35:47,74:48:48
Expected Output would simply be the data in the CSV file with the date in the first column
DD/mm/yyyy,Agent,Tickets assigned,Tickets resolved,Tickets reopened,Tickets reassigned,First response SLA %,Resolution SLA %,FCR %,Private notes,Responses,Average 1st response time,Average response time,Average resolution time
DD/mm/yyyy,Agent 1,1,2,,1,,100,100,,1,,,17:32:49
DD/mm/yyyy,Agent 2,11,14,3,,88,86,67,3,15,06:09:35,04:04:20,19:44:18
DD/mm/yyyy,Agent 3,18,25,7,,100,80,68,6,14,01:28:29,02:35:47,74:48:48
Use Array.unshift for each row:
const csvData = Utilities.parseCsv(attachment.getDataAsString());
const date = Utilities.formatDate(message.getDate(), "GMT+1", "MM/dd/yyyy");
csvData.forEach(row => row.unshift(date));

Pulling scattered data from changing spreadsheet

I'm trying to write a script which has multiple parts. The current part (function copyOver) supposed to open a spreadsheet (by ID), extract specific data from it and insert it into a Master spreadsheet.
The part I'm stuck with is that the spreadsheet changes daily. Some days there are cells containing data of "Sea Water", other days there aren't any. Ideally, I was trying to write a script which loops through the sheet looking for the specific tab names in cells (for example: Sea water, chlorine content...) and extract the data from the row below, up until the second tab and so on. I would only need very specific data, like data from "C6", "E6", "F10", but these cells are always changing so I have to look for them by using the tab names in cells above them. There would be multiple arrays for each tab and the data coming with it.
Would that be possible to extract data this way and put them into an array containing the tab value as the header or title and the data connected to that specific tab.
var sourceID = "source sheet ID";
var main = SpreadsheetApp.openById("master sheet ID"); // MASTER
var source = SpreadsheetApp.openById(sourceID); //the workbook you're copying from
var mainsheet = main.getSheetByName("Lab Data"); // the sheet you want to copy the stuff into
var sourcesheet = source.getSheetByName("ANALYSIS REPORT"); // the sheet you're copying from
var dataRange = sourcesheet.getDataRange().getValues(); // gets sheet as data range
// finds SEA WATER tab in cell and gets the range below until the end of the document
// this is the range I need to find the rest of the tabs and the data under them.
var cont = "SEA WATER";
var i = [];
for (var y = 0; y < dataValues.length; y++){
if(dataValues[y] == cont){
i.push(y);
} // end if
} // end for
Logger.log(i);
var Row = Number(i)+Number(dataRange.getRow()); // gets the row number of SEA WATER tab
Logger.log("row number: " + Row);
var swLast = sourcesheet.getLastRow();
var swRange = sourcesheet.getRange(Row,2,swLast,11);
var swValues = swRange.getValues(); // range of needed information
Logger.log("sw: " + swValues);
var con2 = "SW outlet SW from Coarse filtration"; // looking for the secondary tab, within the
range
I got from the first loop
var res2 = [];
for(var i2 = 0; i2 < swValues.length; i2++) {
if(swValues[i2][4] === con2) res2.push(data[i2])
var row2 = Number(i2)+Number(swRange.getRow());
Logger.log("row2 " + row2);
} // for end
var look1 = "SW outlet SW from Coarse filtration ";
for(var i1 = 0; i1<dataRange.length;i1++){
if(dataRange[i1][1] == look1){
return i1+1;
}
}
Logger.log((i1+1));
} // end of function
EDIT: Here's a link to a sheet. Required data starts from row 237, until row 268 - but this can change every day. (Deleted the information for privacy reasons.)
Basically, I need all the cells with "x" in them, preferably together with the title cells above them, so I'll know what data it is.
The following code loops through your headers in column B and finds the row in which your header of interest is located
Subsequently, it copies the data starting from this row to the last data row into the sheet "Lab Data":
function myFunction() {
var sourceID = "source sheet ID";
var main = SpreadsheetApp.openById("sourceID"); // MASTER
var source = SpreadsheetApp.openById(sourceID); //the workbook you're copying from
var mainsheet = main.getSheetByName("Lab Data"); // the sheet you want to copy the stuff into
var sourcesheet = source.getSheetByName("ANALYSIS REPORT"); // the sheet you're copying from
var dataRange = sourcesheet.getDataRange()
var dataValues=dataRange.getValues(); // gets sheet as data range
for (var a = 0; a < dataValues.length; a++){
if(dataValues[a][1]=="SEA WATER"){
var Row=a+1;
break;
}
}
if(Row){
var swLast = sourcesheet.getLastRow();
var swRange = sourcesheet.getRange(Row,2,swLast,11);
swRange.copyTo(mainsheet.getRange(mainsheet.getLastRow()+1, 2));
}
} // end of function
I hope this helps to solve your issue.

setFormulas and rangeList

I'm trying to setFormulas over a range of non-contiguous cells. I need a formula (they're all different) set every 30 cells in a single column (c).
It works to setFormula for each cell, but creating 56 variables seems unnecessary. I can get the formulas but not set them as intended. I also tried using getRangeList but I'm not sure that does what I think it does. Any advice?
function test() {
var spreadsheetU09U10 = SpreadsheetApp.openById('some url');
var sheetU09 = spreadsheetU09U10.getSheetByName('TEST');
var sheetU10 = spreadsheetU09U10.getSheetByName('U10');
var sheetDATA = spreadsheetU09U10.getSheetByName('Sheet4');
//U09 SHEET
//var rangeListU09 = sheetU09.getRangeList(['C4','C34','C64','C94','C124','C154','C184','C204','C234','C264','C294','C324','C354','C384','C404','C434','C464','C494',
//'C524','C554','C584','C604','C634','C664','C694','C724','C754','C784']);
//Logger.log(rangeListU09);
var startRow = 4;
var startColumn = 3;
var numRows = sheetU09.getLastRow();
var numColumns = 1;
var range = sheetU09.getRange(startRow, startColumn, numRows, numColumns);
var getFormulasU09 = sheetDATA.getRange('C30:C57').getFormulas();
//Logger.log(getFormulasU09);
Logger.log(getFormulasU09.length);
for (var i = 0; i < getFormulasU09.length; i++) {
var setFormulasU09 = range.setFormulas(getFormulasU09);
Logger.log(setFormulasU09);
startRow = startRow + 29;
}
It isn't clear exactly where the formulas you are using are originating from, but the RangeList class can help reduce the read time, even if you use it just to call getRanges. If the formula is the same in R1C1 format, then you can very effectively use RangeList#setFormulaR1C1.
Assuming you have formulas in one region that must be written verbatim in a disjoint set of cells:
const wb = SpreadsheetApp.getActive();
// Assuming only text formulas, not actual "entered" formulas
const formulas = wb.getSheetByName("formulas").getDataRange()
.getValues()
.map(function (row) { return row[0]; });
const sheet = wb.getSheetByName("some name");
const destinations = [
// Depending on the relationship between destinations, one could programmatically generate these
];
// Efficiently acquire references to multiple disjoint Ranges
const rl = sheet.getRangeList(destinations);
// Assume the i-th formula goes in the i-th range
rl.getRanges().forEach(function (rg, i) {
rg.setFormula(formulas[i]);
});
// The RangeList makes uniformly formatting these disjoint ranges extremely simple
rl.setFontWeight('bold');
...
Reference
- RangeList
You want to put formulas to the individual cells.
You want to put 28 formulas to cells of ['C4','C34','C64','C94','C124','C154','C184','C204','C234','C264','C294','C324','C354','C384','C404','C434','C464','C494', 'C524','C554','C584','C604','C634','C664','C694','C724','C754','C784'] in the sheet of TEST.
If my understanding is correct, how about using values.batchUpdate of Sheets API? The flow of this script is as follows.
Set range list as 1 dimensional array.
Retrieve formulas.
Create request body for sheets.spreadsheets.values.batchUpdate.
In order to use this script, please enable Sheets API at Advanced Google Services and API console. You can see about how to enable Sheets API at here.
Sample script:
function test() {
var spreadsheetId = "### spreadsheetId ###"; // Please set this.
var sheetName = "TEST";
var spreadsheetU09U10 = SpreadsheetApp.openById(spreadsheetId);
var sheetU09 = spreadsheetU09U10.getSheetByName(sheetName);
// var sheetU10 = spreadsheetU09U10.getSheetByName('U10'); // This is not used in this script.
var sheetDATA = spreadsheetU09U10.getSheetByName('Sheet4');
var rangeListU09 = ['C4','C34','C64','C94','C124','C154','C184','C204','C234','C264','C294','C324','C354','C384','C404','C434','C464','C494', 'C524','C554','C584','C604','C634','C664','C694','C724','C754','C784'];
var getFormulasU09 = sheetDATA.getRange('C30:C57').getFormulas();
rangeListU09 = rangeListU09.map(function(e) {return sheetName + "!" + e});
var resource = {
data: rangeListU09.map(function(e, i) {return {range: e, values: [[getFormulasU09[i][0]]]}}),
valueInputOption: "USER_ENTERED",
};
Sheets.Spreadsheets.Values.batchUpdate(resource, spreadsheetId);
}
Note:
From your question, I'm not sure about the detail formulas. If the a1Notation of each formulas is required to be modified, can you provide a sample spreadsheet including the formulas?
Reference:
sheets.spreadsheets.values.batchUpdate
If I misunderstand your question, please tell me. I would like to modify it.
I'm assuming that you want to copy the whole column starting from the cell locations in the array. That wasn't really clear to me.
function test109() {
var ss=SpreadsheetApp.getActive();
var shU09=ss.getSheetByName('35');//formulas get copied into here starting at row 4
var shDATA=ss.getSheetByName('36');//formulas stored in here C30:C57
var fA=shDATA.getRange('C30:C57').getFormulas();
var dA=['C4','C34','C64','C94','C124','C154','C184','C204','C234','C264','C294','C324','C354','C384','C404','C434','C464','C494','C524','C554','C584','C604','C634','C664','C694','C724','C754','C784'];
for(var i=0;i<dA.length;i++){
var rgs=Utilities.formatString('%s:%s',dA[i],shU09.getRange(dA[i]).offset(fA.length-1,0).getA1Notation());//this uses range.offset to calculate the correct range in A1Notation.
shU09.getRange(rgs).setFormulas(fA);
}
}
As it turns out I just noticed that there are 28 locations and 28 formulas. Perhaps that was intentional and you want to copy a different formula in each location then this version would do that.
function test109() {
var ss=SpreadsheetApp.getActive();
var shU09=ss.getSheetByName('35');//formulas get copied into here starting at row 4
var shDATA=ss.getSheetByName('36');//formulas stored in here C30:C57
var fA=shDATA.getRange('C30:C57').getFormulas();
var dA=['C4','C34','C64','C94','C124','C154','C184','C204','C234','C264','C294','C324','C354','C384','C404','C434','C464','C494','C524','C554','C584','C604','C634','C664','C694','C724','C754','C784'];
for(var i=0;i<dA.length;i++){
shU09.getRange(dA[i]).setFormula(fA[i][0]);
}
}
Range Offset

Resources