Improve spreadsheet filtering script performance - google-apps-script

I have a spreadsheet where first column contains names and next 17 contains 0, 1 or are empty. Every row looks like this:
foobar 0 0 0 1 0 1 // and so on
I need to make function, called from menu, that shows the user only rows with 1 in the target column (arg1). Here is code:
var ssBase = SpreadsheetApp.getActiveSheet();
var last = ssBase.getLastRow() ;
var data = ssBase.getDataRange().getValues();
function SkillsFilter(arg1){
ssBase.showRows(1, last+1);
for (var i=1; i < last; i++){
if (data[i][arg1] != "1"){
ssBase.hideRows(i+1);
}}}
This function doesn't perform as fast as I'd like. How should I increase performance? Will cache help me or something else?

You're making many calls to the Spreadsheet services within your for loop - if you can change those many operations into one, you'll see a great speed improvement. Read Best Practices for some background and guidance.
I suggest that you reconsider the approach of hiding & showing various rows of data. Instead, you could display a filtered list, and manipulate that filter using your menu functions. Let's say the data you have looks like this...
On a second tab in the spreadsheet, you could provide the filtered version of your list. The following formula in cell A2 would create a filtered list of data from the orignal data sheet (called "Master" in this example):
=filter(Master!A2:R;Master!D2:D="1")
To create that filter programmatically, use the setFormulaR1C1() function. Here is a function that you could call from your menu items to set the filter for any particular column.
/**
* Sets the filter in cell A2 of the sheet named "Filter" to display
* only rows with a number 1 in the indicated column.
*
* #param {number} column The "skill" column to filter for
*/
function setFilter(column) {
column = column | 2; // Use first "skill" column as default
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Filter");
var formula = "Master!R[0]C[0]:C[17];Master!R[0]C["
+column
+"]:C["
+column
+"]=1";
sheet.getRange("A2").setFormulaR1C1(formula);
}

This piece of code will show all rows that contain the number 1, thought the column range:
function mySort() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
// set col and row
var col = sheet.getLastColumn(), row = sheet.getLastRow()-1;
// hide all rows
sheet.hideRows(2, row);
// itterate through columns
for(var k=0; k<col; k++) {
this.data = sheet.getRange(2, 1, row, col)
.sort({column: parseInt(k+1), ascending: true}).getValues();
//set counters
var cFirst=0, cSecond=0;
// itterate to find number of "1" rows in column k
for(var i=0; i<row; i++) {
if(this.data[i][k] == 1) {
cFirst++;
} else {
cSecond++;
}
}
// calculate rowIndex
var rIndex = row-cSecond;
// show (unhide) only "1" rows
sheet.showRows(rIndex+2, cFirst);
}
}
See example file I created: Show the one's. When the file is opened, it will add another menu item called Sorting. There you can play around and see what happens.

Related

How to enter an entire array values into a cell in google spreadsheet?

I have a google spreadsheet with values. I want to enter the values from 5 cells into one other cell, one after the other.
Let' say I have five different cells in a column like this.
1
2
3
4
5
So I created an array and pushed the values into the array. Then I have a script to assign cell.setValue(array) to the cell. The cell displays only the first value of the array. I want to display all values one below the other, but inside the same cell.
How do I do it with script?
I can do it like the below version, but the execution time is high.
for(var i = 0; i<variations.length; i++)
{
var cval = s.getRange(lsrn, 3).getValue();
if(i ==variations.length-1){s.getRange(lsrn, 3).setValue(cval+variations[i]);}
else{s.getRange(lsrn, 3).setValue(cval+variations[i]+"\n");}
}
Here, the variations is the array.
I want to do it without having to copy the values of the cell time and time again.
How do I do it?
Your code snippet is slow be cause of the many calls to SpreadsheetApp, see Best Practices
You can singnificatly optimize it by retrieving the value of getRange(lsrn, 3) only once
Then feed the values of your array plus \n into a dummy string within the loop
Set the result into your spreadsheet only once - after exiting the loop
Sample:
function myFunction() {
var s = SpreadsheetApp.getActiveSheet();
var lsrn = 1;
var variations = [1,2,3,4,5];
var string = "";
var cval = s.getRange(lsrn, 3).getValue();
for(var i = 0; i<variations.length; i++)
{
if(i ==variations.length-1){
string+=variations[i];
}
else{
string+=variations[i]+"\n";
}
}
s.getRange(lsrn, 3).setValue(cval+"\n"+string);
}

Google App Script Catch Row Number

I have looked everywhere and seen the related questions on this board, but I am really stuck.
I need to capture the actual row value of a Sheet at is iterates through a for loop based on an if statement.
In other parts of the code, I have to create a copy of a Form's response sheet and move it to a folder. I am trying to log the file id of the new file that is being created to a sheet that already exists (and in which the scripts are being called from a menu).
I can get the data and the values and iterate through them and see everything in the Logs. And based on what I saw in other similar questions and such, I tried the getActiveRange().getRow() part - but it only works for the exact row that the cursor is on when the form is open. I need to be able to dynamically set the ActiveRow variable based on the row that the script is currently looking at as it goes through the for loop.
I can not use the for loop variable as my current row (j+1), because the if statement is only looking for a string value "NOT FOUND" in the column - so the "j" variable value would not necessarily match the row value to update.
Thank you so much for any assistance!!
// Get the active spreadsheet and the active sheet
var ssEXD = SpreadsheetApp.getActive().getSheetByName('FormData');
var lastRowEXD = ssEXD.getLastRow();
var lastColumnEXD = ssEXD.getLastColumn();
var formIDValues = ssEXD.getSheetValues(2, 1, lastRowEXD-1, lastColumnEXD);
//Loop through the returned values
for(var j = 0; j < formIDValues.length; j++) {
//if NOT FOUND is true, do stuff
if (formIDValues[j][6] == "NOT_FOUND") {
//need this variable to be the exact row of the sheet value data
var ActiveRow = ssEXD.getActiveRange().getRow();
//I need to use the ActiveRow in the getActiveRange to update the sheet with some information.
ssEXD.getRange(ActiveRow,6,1).setValue("testing");
}
}
If I'm reading your question correctly your data has a column names on the first row and data begins on the second row.
So I think this will accomplish what you want.
var ssEXD = SpreadsheetApp.getActive().getSheetByName('FormData');
var lastRowEXD = ssEXD.getLastRow();
var lastColumnEXD = ssEXD.getLastColumn();
var dataR = ssEXD.getDataRange();
var dataA = dataR.getValues();
for(var j = 1; j < dataA.length; j++)
{
if (dataA[j][6] == "NOT_FOUND")
{
dataA[j][6] = "testing";
}
}
dataR.setValues(dataA);
var ActiveRow = j + 2;
Once an array(formIDValues) has been extracted from sheet, the array has no more relation to that sheet. One must adjust the index of the item being worked on in the array to the row number in the spreadsheet.
j - the array index - starts at zero which is the second row in this spreadsheet, therefore j + 2
Your concern about the j variable is unwarranted. j is incremented for every pass through the loop whether or not any test has been passed.

Find string and get its column

Let's say I have a lot of columns and one of them contains "impressions" string (on row 3). What I need to do is to:
1) Find the cell with "impressions" string
2) Get column number or i.e. "D"
3) Based on what I got paste a formula into i.e. D2 cell which gets AVERAGE from a range D4:D*last*
I couldn't find it anywhere so I have to ask here without any "sample" code, since I have no idea on how to achieve what I want. (3rd one is easy but I need to get that "D" first)
There's no way to search in Google Apps Script. Below is a function that will accomplish the first 2 parts for you (by iterating over every cell in row 3 and looking for "impressions"):
function findColumnNumber() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1'); // insert name of sheet here
var range = sheet.getDataRange(); // get the range representing the whole sheet
var width = range.getWidth();
// search every cell in row 3 from A3 to the last column
for (var i = 1; i <= width; i++) {
var data = range.getCell(3,i)
if (data == "impressions") {
return(i); // return the column number if we find it
}
}
return(-1); // return -1 if it doesn't exist
}
Hopefully this will allow you to accomplish what you need to do!
The indexOf method allows one to search for strings:
function findColumnNumber() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet() //whatever tab the code is run on
var data = sheet.getDataRange().getValues();
var header_row_num = 1; // TODO: change this to whichever row has the headers.
var header = data[header_row_num -1] //Remember JavaScript, like most programming languages starts counting (is indexed) at 0. For the value of header_row_num to work with a zero-index counting language like JavaScript, you need to subtract 1
//define the string you want to search for
var searchString = "impressions";
//find that string in the header and add 1 (since indexes start at zero)
var colNum = header.indexOf(searchString) + 1;
return(colNum);

Google App Scripts(spreadsheet) - consolidate data into one sheet

Here is the set up
We have a contest with all employees based on project scores. Each project has two categories of employees(4 employees per category) and two scores(one for each category of employee).
I need to grab all the scores for the employees and output it into a spreadsheet. The following spreadsheet has misc. columns removed
Sheet Explanation
The sheet labeled "Example data" is the source we will be pulling data from
We need to match Editor and Editor Score
We need to match Webmaster and webmaster score
The sheet labeled "Example output" is what I want to be generated in another spreadsheet named "Contest Result" with the sheet name from the source sheet(They are named by date ranges).
We need to compile each employee by the categories
We need to compile all scores to the row for a singular employee
I had found this Removing Duplicates Article that seemed to at least process the information and compare it in a manner that I think this can be done, but am failing to make it work due to being inexperienced.
Did not know what Transpose was till someone commented :)
Here is the solution in another article for how to pull it off with Google Apps Script and with using the spreadsheet option.
How to split and transpose results over 2 columns
Here is the actual code I used to make it work(it is a little horrible but I tried) suggestions on how to improve this?:
function createScoreSheet() {
// Get Source spreadsheet
var source = SpreadsheetApp.getActive();
var sourceSheet = source.getActiveSheet();
var SourceActivate = sourceSheet.activate();
// Set Sheet Name
var sheetName = sourceSheet.getSheetName();
// Set Values to transpose and combine
var sourceEditor = sourceSheet.getRange("C1:C51");
var sourceWeb = sourceSheet.getRange("D1:D51");
var editorScores = sourceSheet.getRange("L1:L51");
var webScores = sourceSheet.getRange("K1:K51");
// Used to create a new spreadsheet
var sheetNameNew = sheetName + " Scores";
var createSheet = SpreadsheetApp.getActive().insertSheet(sheetNameNew,0);
var targetSheet = source.getSheetByName(sheetNameNew);
var totalScore = 1;
// s is the the counter we use to stick values into the rows
var s = 3;
// n is the the counter we use to stick values into the columns
var n = 1;
// loops through twice, once for the editor values, once for the webmaster
for (var j = 1; j<3; j++) {
if (j == 1) {
// grab values for the editors and copy to new sheet
sourceEditor.copyTo(targetSheet.getRange("A1"));
editorScores.copyTo(targetSheet.getRange("B1"));
// delete the header row then sort the column ASC by default
targetSheet.deleteRow(n);
targetSheet.sort(1);
// Find the last value to see how many scores we have
var lastRow = targetSheet.getLastRow();
}
if (j == 2) {
// grab values for the webmasters and copy to new sheet
sourceWeb.copyTo(targetSheet.getRange(n,1));
webScores.copyTo(targetSheet.getRange(n,2));
// delete the header row then sort the column ASC by default
targetSheet.deleteRow(n);
lastRow = targetSheet.getLastRow();
targetSheet.getRange(n,1,lastRow,2).sort(1);
lastRow = targetSheet.getLastRow();
}
// this loop will check to see if the value of the cell is equal to the next on the list and move the score
for (var i = 1; i<lastRow+1; i++) {
// Grab the name of the current row and the next
var firstName = targetSheet.getRange(n,1).getValue();
var nextName = targetSheet.getRange(n+1,1).getValue();
// Grab the scores
var oldScore = targetSheet.getRange(n+1,2);
var newScore = targetSheet.getRange(n,s);
// Loop to check to see if the firstname is blank and break to find the next value
if (firstName === "") {
break;
}
// checks to see if name is equal to the next then shifts then copies the score and adjust the horizontal position
if (firstName == nextName) {
totalScore = oldScore + newScore;
oldScore.copyTo(newScore);
s = s+1;
targetSheet.deleteRow(n+1);
}
// resets horizontal position for the score and increases the row
else {
s=3;
n=n+1;
}
}
// kills remaining rows
targetSheet.deleteRows(n,37);
}
}
I would do it like this:
If you want to generate the names automatically as well, then write this to the output sheet A1:
=unique('Example Data'!B2:B) - This function simply generate the editor names to the A2-A5 cells.
Now write this to the B2 cell:
=transpose(filter('Example Data'!E:E,'Example Data'!B:B=A2)) - This function filters the editor points according to the given name in the beginning of the row (in this case its A2). Then transposes the result in a horizontal form. To get the result for the other rows, simply populate this formula down.
I think you can find out the rest. :)
Hope it helps.

Reducing Google Apps Script execution time with using an array?

I wrote a script to periodically copy data from one column depending on if each cell was determined to have current data (Designated as ALIVE in another column), and place that data in another column in a different sheet. The script doesn't exceed the execution time, however I was wondering if there was a way to make it faster by utilizing Arrays.
I appreciate the help, I'm new to Google Apps Script programming but plugging along. Many thanks in advance for the advice.
function copyFunctionDATA() {
var defSheet1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("(DATA)")
var defSheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("(DATAdead)")
var numLastRow = 60
for (var x=11; x<=numLastRow; x++) {
var srcRange = defSheet1.getRange(x,1);
var srcRange2 = defSheet1.getRange(x,1);
var value = srcRange.getValue();
var value2 = srcRange2.getValue();
if (value2.indexOf("ALIVE") !== -1) {
defSheet2.getRange(x,1).setValue(value);
}
}}
Transposing in 2D array is very simple. The main difference is the way data is indexed : ranges count from 1 and arrays count from 0.
So to transpose your code you should get 2 arrays (one for each sheet) and iterate the corresponding cells, change the value depending on your condition and write back the array to the spreadsheet to update it.
Here is a rough transpose of your code with a couple of comments to explain : (some variables ought to be renamed for clarity)
function copyFunctionDATA() {
var defSheet1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("(DATA)").getDataRange().getValues();// read the whole sheet in a 2D array
var defSheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("(DATAdead)").getDataRange().getValues();// read the whole sheet in a 2D array
var numLastRow = 59 ; // I suppose you intentionally limit to the 60 first rows ?
for (var x=10; x<=numLastRow; x++) { // starting from row 11 >> becomes 10
var value = defSheet1[x][0];
var value2 = defSheet1[x][0]; // you made a mistake in your code : you define 2 identical ranges !! change it to your need : 0 is column A, 1 is B etc...
if (value2.indexOf("ALIVE") !== -1) {
defSheet2[x][0] = defSheet1[x][0];
}
}
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("(DATAdead)").getRange(1,1,defSheet2.length,defSheet2[0].length).setValues(defSheet2);// write back defSheet2 array to sheet (DATAdead)
}
EDIT : if you want to overwrite only the first column in defSheet2 change simply the range definition for this sheet, for example like this :
var defSheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("(DATAdead)").getRange('A1:A').getValues();// read the whole sheet in a 2D array