I have a google spreadsheet data in form of array. Now I want to push the array position I used for loop which is working fine on small data but when the data length is increased it result in delay.
Is there a faster way to push the array position in the array.
Here is the code which I am currently using:-
var ss = SpreadsheetApp.openById('19zxxxxxxxxxxxxxxxxxxxxxxxxOI');
var sheet1 = ss.getSheetByName('Sheet2');
var data = sheet1.getRange("A:H").getValues();
var email = Session.getActiveUser().getEmail();
for(var i = 0; i < data.length; i++)
{
data.unshift(i+1);
} // this for loop takes too much time.
data = data.filter(function(item){return item[7] == email});
var x = data.map(function(val){
return val.slice(0, -7);
})
Logger.log(x)
return x;
}
I believe your goal as follows.
If data.unshift(i+1) is data[i].unshift(i+1) as TheMaster's comment, you want to retrieve the values of the column "A" when the value of column "G" is the same with email. At that time, you want to add the row number to the 1st index of the row value.
From your script, I understood like this.
You want to reduce the process cost of this situation.
For this problem, how about this solution?
Pattern 1:
In this pattern, your script is modified. In this case, the result values are retrieve by one loop.
Sample script:
function myFunction() {
var ss = SpreadsheetApp.openById('19zxxxxxxxxxxxxxxxxxxxxxxxxOI');
var sheet1 = ss.getSheetByName('Sheet2');
var data = sheet1.getRange("A1:H" + sheet1.getLastRow()).getValues(); // Modified
var email = Session.getActiveUser().getEmail();
const res = data.reduce((ar, [a,,,,,,g], i) => { // Modified
if (g == email) ar.push([i + 1, a]);
return ar;
}, []);
Logger.log(res)
return res;
}
Pattern 2:
In this pattern, as other method, TextFinder and Sheets API are used. In this case, the size of base data by searching email with TextFinder can be reduced. And each values are retrieved by one API call using Sheets API.
Sample script:
Before you use this script, please enable Sheets API at Advanced Google services.
function myFunction() {
const spreadsheetId = '19zxxxxxxxxxxxxxxxxxxxxxxxxOI';
const ss = SpreadsheetApp.openById(spreadsheetId);
const sheet = ss.getSheetByName('Sheet2');
const email = Session.getActiveUser().getEmail();
// 1. Retrieve the ranges of rows by searching "email" at the column "G".
const ranges = sheet.getRange("G1:G" + sheet.getLastRow()).createTextFinder(email).findAll();
// 2. Create an object for using with Sheets API.
const reqs = ranges.reduce((o, e) => {
const row = e.getRow();
o.rows.push(row);
o.ranges.push("A" + row);
return o;
}, {rows: [], ranges: []});
// 3. Retrieve values and add the row number.
const res = Sheets.Spreadsheets.Values.batchGet(spreadsheetId, {ranges: reqs.ranges})
.valueRanges
.map((e, i) => ([reqs.rows[i], e.values[0][0]]));
Logger.log(res)
return res;
}
If email is included other string, please use matchEntireCell(true) to TextFinder.
References:
reduce()
Advanced Google services
Method: spreadsheets.values.batchGet
Related
So I wrote some code that goes to a book's ID based on a "dictionary" and takes the data from all the sheets in that book after the second sheet. That data is then aggregated into a single array. My question centers around the idea of improving the for loop in the if statement. That is adding the "supplier name" to the front of the subarray that represents a row in the sheet/data. I was told that this is inefficient as the code has to go through each subarray and add a value.
Is there a more efficient way of doing this? I did it this way because I am copying the values of a range which are stored as an array of arrays. So to add data, I have to reaccess the subarrays. Is it possible to add the new data (supplier name) at the same time that the values are being copied? would this be more efficient? It was recommended to use an arrow function, however, I am not familiar with their usage.
function aggregate() {
var combinedData = []
var idArray = {
"suppliername":"id",
};
for (var supplierName in idArray){
var sBook = SpreadsheetApp.openById(idArray[supplierName]);
var sheets = sBook.getSheets();
for (var index = 2; index <sheets.length; index++){
var sheet = sheets[index];
var dataLength = sheet.getRange("E5:E").getValues().filter(String).length;
if(dataLength != 0){
var dataRange = sheet.getRange(5,2,dataLength,14);
var dataValues = dataRange.getValues();
for (row in dataValues) {
dataValues[row].unshift(supplierName);
};
combinedData = combinedData.concat(dataValues);
};
};
};
var dataLength = combinedData.length;
const dSheet = SpreadsheetApp.openById("id").getSheets()[0];
dSheet.getRange(2,1,dSheet.getMaxRows(),dSheet.getMaxColumns()).clearContent();
var dRange = dSheet.getRange(2,1,dataLength,15);
dRange.setValues(combinedData);
};
In your script, how about the following modification?
From:
for (row in dataValues) {
dataValues[row].unshift(supplierName);
};
combinedData = combinedData.concat(dataValues);
To:
combinedData = combinedData.concat(dataValues.map(e => [supplierName].concat(e)));
or
combinedData = [...combinedData, ...dataValues.map(e => [supplierName, ...e])];
References:
map()
Spread syntax
I'm using app script to get an HTML page
everything works fine
except that I need to exclude column B+C from the range
function doGet() {
return HtmlService.createTemplateFromFile('Index').evaluate()
.setTitle("Performance Efficiency");//We can set title from here
}
//GET DATA FROM GOOGLE SHEET AND RETURN AS AN ARRAY
function getData() {
var spreadSheetId = "Sheet ID"; //CHANGE
var dataRange = "Data!A3:P"; //CHANGE
var range = Sheets.Spreadsheets.Values.get(spreadSheetId, dataRange);
var values = range.values;
return values;
}
In your situation, how about the following modification?
From:
var values = range.values;
To:
var srcValues = range.values;
var temp = srcValues[0].map((_, c) => srcValues.map(r => r[c])).filter((_, c) => ![2, 3].includes(c + 1));
var values = temp[0].map((_, c) => temp.map(r => r[c]));
In this modification, at first, the value of range.values is transposed and remove the columns "B" and "C", and then, the values are transposed. By this, the values except for the columns "B" and "C" can be retrieved.
References:
map()
filter()
I would like to use a global array / variable in my function. The function should be executed as long as IDS are in the array.
In the variable "var files = [...];" there are for example two
IDS, depending on how many files are in the folder.
var files = ['16EdsAx', '16wQxxIc'];
var files = [];
function getListOfId(){
var folderId = "11tjb_odTJ2E_ez";
var filesN = DriveApp.getFolderById(folderId).getFiles();
while (filesN.hasNext()) files.push(filesN.next().getId());
//console.log(files);
}
Don't be intimidated, these two functions only read the DOCs documents
and write them into the corresponding cell.
function getDocItems(docID, identifier){
const body = DocumentApp.openById("13TlciLOZV").getBody(); // >>> The IDS from the array should be used here <<<<
const docText = body.getText();
//Check if search characters are to be included.
let startLen = identifier.start_include ? 0 : identifier.start.length;
let endLen = identifier.end_include ? 0 : identifier.end.length;
//Set up the reference loop
let textStart = 0;
let doc = docText;
let docList = [];
//Loop through text grab the identifier items. Start loop from last set of end identfiers.
while(textStart > -1){
let textStart = doc.indexOf(identifier.start);
if(textStart === -1){
break;
}else{
let textEnd = doc.indexOf(identifier.end) + identifier.end.length;
let word = doc.substring(textStart,textEnd);
doc = doc.substring(textEnd);
docList.push(word.substring(startLen,word.length - endLen));
};
};
//return a unique set of identifiers.
return [...new Set(docList)];
};
//The chewy conversation
function runsies(){
const docID = "13TlciLOZV"; // >>> The IDS from the array should be used here <<<<
const identifier = {
start: `ISIN: `,
start_include: false,
end: `VERRECHNUNGSKONTO`,
end_include: false
};
let results = getDocItems(docID, identifier);
//var commaAdd = results.join("''");
//console.log(results);
const ss = "17a55HCwlO5uF8gkXpG";//The spreadsheet ID
const sheet = "Stock_Data";//The sheet tab name
var activeSheet = SpreadsheetApp.getActiveSheet();
let importToSpredsheet = SpreadsheetApp.openById(ss).getSheetByName(sheet);
const range = activeSheet.getRange(6,1,results.length,1);
range.setValue(results);
};
Here you can find the tutorial where I got this code from. HERE
I always used the exact docs id in the code. But now I would like to use the ids from the array from the getListOfId () function. The information from the files should all be in different cells, ideally all in column A one below the other.
So my questions are:
How can I refer to the IDS in the other two functions?
The function should be repeated until all IDS have been used and all files have been read out and entered in the spreadsheet, but how?
I believe your goal as follows.
You want to retrieve the Google Document IDs from the function of getListOfId.
In this case, the IDs returned from getListOfId are always the file IDs of Google Document.
You want to use the file IDs to docID of let results = getDocItems(docID, identifier); in the function of runsies.
You want to put the values retrieved from the function of getDocItems to the sheet of Stock_Data in the Google Spreadsheet.
Modification points:
In this case, I would like to propose the following flow.
Retrieve the file IDs from getListOfId.
In this modification, the file IDs retrieved from getListOfId are used in runsies.
Put the file IDs to getDocItems using a loop.
Put the result values to the Spreadsheet.
When I saw your script for putting values to the Spreadsheet, the values are put to the active sheet. If you want to put the values to the sheet of Stock_Data in the Google Spreadsheet of const ss = "17a55HCwlO5uF8gkXpG";, it is required to modify the script.
And also, in your script, by const range = activeSheet.getRange(6,1,results.length,1); and range.setValue(results);, the 1st element in the array of results is put the number of times of the length of results from the cell "A6". When you want to put the values from the row 6, it is required to modify the script.
When above points are reflected to your script, it becomes as follows.
Modified script:
getListOfId()
Please set your folder ID.
function getListOfId(){
var folderId = "###"; // Please set your folder ID.
var filesN = DriveApp.getFolderById(folderId).getFiles();
var files = [];
while (filesN.hasNext()) files.push(filesN.next().getId());
return files;
}
runsies()
Please set your Spreadsheet ID.
function runsies(){
const docIDs = getListOfId(); // Here, the file IDs are retrieved from `getListOfId`.
const identifier = {
start: `ISIN: `,
start_include: false,
end: `VERRECHNUNGSKONTO`,
end_include: false
};
if (docIDs.length == 0) return;
const results = docIDs.map(id => getDocItems(id, identifier)); // Here, the retrieved file IDs are used in a loop.
const ss = "###"; // Please set your Spreadsheet ID.
const sheetName = "Stock_Data"; //The sheet tab name
const sheet = SpreadsheetApp.openById(ss).getSheetByName(sheetName);
const range = sheet.getRange(sheet.getRange(6,1).isBlank() ? 6 : sheet.getLastRow() + 1,1,results.length,results[0].length);
range.setValues(results);
}
In this case, when docIDs has not file IDs, the script is stopped.
In this modified script, from your script, the retrieved values results are put from the row 6 on the sheet of Stock_Data in the Google Spreadsheet const ss = "###". When the values has already been existing from the row 6, the values are appended.
getDocItems(docID, identifier)
From:
const body = DocumentApp.openById("13TlciLOZV").getBody();
To
const body = DocumentApp.openById(docID).getBody();
Note:
Please use this modified script with enabling V8 runtime.
If above modification is not the result you expect, can you show the whole script and the detail of your goal? By this, I would like to confirm it.
References:
map()
setValues(values)
I have rows of data in column A containing cells starting with AR. I would like any cell that contains AR to be deleted. I have script already but this only deletes exact matches
So example is AR12345 in Column A & A12345. So it should ONLY delete the cell row with AR and not just A
function DeleteAny() {
var sheet = SpreadsheetApp.getActive();
sheet.setActiveSheet(sheet.getSheetByName('MULTI KIT DATA'), true);
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var rowsDeleted = 0;
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
// I cant put AR here because it wont delete anything. the AR numbers keep changing also
if (row[14] == '') {
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
};
I have searched but cannot find anything.
How about using javascript Array#filter ? This is probably simplest for simple data.
// filter values
let values = sheet.getDataRange().getValues()
.filter(row => !row.find(v => v.match(/^AR/)))
// clear range
sheet.getDataRange().clear()
// write back filtered values
sheet.getRange(1, 1, values.length, values[0].length).setValues(values)
How about the following modification?
Pattern 1:
In this pattern, as a simple modification, your script is modified.
From:
if (row[14] == '') {
To:
if (row[0].length > 1 && row[0].substr(0, 2) == "AR") {
In this modification, the top 2 characters are retrieved with row[0].substr(0, 2).
If AR is included in the inner value, if (row[0].length > 1 && row[0].includes("AR")) { might be suitable.
Note:
In your script, row[14] is used. But in your question, the values are in the column "A". So I used row[0]. If you want to check other column, please modify it.
Pattern 2:
In this pattern, as other sample script, TextFinder and Sheets API are used. When you use this, please enable Sheets API at Advanced Google services.
function myFunction() {
const sheetName = "MULTI KIT DATA"; // Please set the sheet name.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(sheetName);
const sheetId = sheet.getSheetId();
const requests = sheet
.getRange(`A1:A${sheet.getLastRow()}`)
.createTextFinder("^AR")
.matchCase(true)
.useRegularExpression(true)
.findAll()
.map(r => r.getRow())
.reverse()
.map(r => ({deleteDimension:{range:{sheetId:sheetId,startIndex:r - 1,endIndex:r,dimension:"ROWS"}}}));
Sheets.Spreadsheets.batchUpdate({requests: requests}, ss.getId());
}
In this sample script, the ranges of values which have AR at the top 2 characters are retrieved with TextFinder. And the request body is created using the retrieved ranges, and then, it requests to Sheets API with the request body. By this, the rows you want to delete are deleted.
When there are a lot of rows you want to delete, the process cost of this sample script might be low.
References:
substr()
includes()
TextFinder
Method: spreadsheets.batchUpdate
DeleteDimensionRequest
My code below works to pass the variable emails to the GmailApp as recipients of an email. This filters out any row that isn't blank. I have run into a new issue where it is encountering bad data. Is there a way to have it filter out anything that isn't a number instead of blanks?
var originalSpreadsheet = SpreadsheetApp.getActive();
//var contacts = originalSpreadsheet.getSheetByName('Sheet1');
//var numRows = contacts.getLastRow();
//var emailTo = contacts.getRange(2, 1, numRows, 1).getValues();
var ss = SpreadsheetApp.getActiveSpreadsheet();
const contacts = ss.getRange('A2:A17').getValues(); // 2D array
const filtered = contacts.filter ( function (row) {
return (row[0] != "");})
const emails = filtered.map(function (nameRow) { return nameRow[0] + "#gmail.com"; });
Could you use something like,
const filtered = contacts.filter(function (row) {
return !isNaN(row[0]);
});
isNaN will return true on all non-numbers not just NaN values.
(Google doesn't currently support arrow functions, should this change, or you need the solution in a different context, the following might work too)
const filtered = contacts.filter(row => !isNaN(row[0]));