function checkWho(n,b)
{
// n and be are comparing two different cells to check if the name is in the registry
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var glr = sheet.getLastColumn();
var glr2 = sheet.getLastRow();
for(var i = 9; i <= glr; i++)
{
for(var z = 10; z<= glr2; z++)
{
if( n == b)
{
var courts = sheet.getRange(3,i).getValue();
var times = sheet.getRange(z,10).getValue();
return(b+ " "+"has booked"+" "+ courts+" "+"at"+times);
}
}
}
}
I am having issues printing out the values contained in var courts and var times. My code consists of two for loops iterating through columns and rows and eventually spitting out the users name, what court they've booked and at what time. As of now the name gets printed, but the courts and the times don't.
it currently prints: "(name) has booked at"
When I want it to print:" (name) has booked court 1 at 4:30"
Any help on the situation?
What is happening is that the the nested for statements are overwriting the result. It's very likely that the court and time are "" (empty strings) because the iteration is done from a start column/row and repeated for the next columns/rows. It's very common that the last column/rows are empty.
Side notes:
The script include a comment mentioning that custom function arguments are cells but custom function can't use cells as argument in the same sense of getCurrentCell(). Custom functions arguments types could be String, Number, Date or Array.
It doesn't make sense to compare the arguments inside the nested for statements as they doesn't change on each iteration.
Including a return inside the nested for statement will stop the iterations. As the arguments are not iteration dependent, only the first iteration is made for the case considered in the question.
If you return a string and your matter is to print those variables, then replace your return statement like this.(same as java script ES6 )
return(`${b} has booked ${courts} at ${times}`);
App script is grooming scripting language. My suggestion is working properly now.
Related
I'm trying to create a function in Sheets that combines a "Vlookup" and "Match" combination that I use frequently.
I want to use my function, "Rates" to accept 1 argument and return a combination of Vlookup and Match, that always uses the same values.
Vlookup(argument, DEFINED RANGE (always stays the same defined range), match(A1 (always cell A1), DIFFERENT DEFINED RANGE, 0), FALSE)
I have tried creating a script, but have no experience coding, and I receive an error that "vlookup is not defined"
function ratesearch(service) {
return vlookup(service, Rates, Match($A$1,RatesIndex,0),FALSE);
}
Actual results: #ERROR!
ReferenceError: "vlookup" is not defined. (line 2).
function findRate() {
var accountName = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(1,1).getValue(); //determine the account name to use in the horizontal search
var rateTab = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Rates'); //hold the name of the rate tab for further dissection
var rateNumColumns =rateTab.getLastColumn(); //count the number of columns on the rate tab so we can later create an array
var rateNumRows = rateTab.getLastRow(); //count the number of rows on the rate tab so we can create an array
var rateSheet = rateTab.getRange(1,1,rateNumRows,rateNumColumns).getValues(); //create an array based on the number of rows & columns on the rate tab
var currentRow = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getActiveCell().getRow(); //gets the current row so we can get the name of the rate to search
var rateToSearch = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(currentRow,1).getValue(); //gets the name of the rate to search on the rates tab
for(rr=0;rr<rateSheet.length;++rr){
if (rateSheet[rr][0]==rateToSearch){break} ;// if we find the name of the
}
for(cc=0;cc<rateNumColumns;++cc){
if (rateSheet[0][cc]==accountName){break};
}
var rate = rateSheet[rr][cc] ; //the value of the rate as specified by rate name and account name
return rate;
}
Optimization points for Alex's answer:
Never forget to declare variables with var, const or let (rr and cc). If you omit the keyword, the variables will be global and cause you a lot of trouble (as they will not reset after the loop finishes). The best way is to use block-scoped let.
Following #1, do not rely on out-of-scope variables (rateSheet[rr][cc]).
You do not need to call SpreadsheetApp.getActiveSpreadsheet() multiple times - that's what variables are for. Call once, then reuse.
getRange(1,1,<last row>, <last col>) is equivalent to a single getDataRange call.
use find or findIndex method to avoid verbose loops.
With the points applied, you get a clean and optimized function to use:
const findRate = () => {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const accountName = ss.getActiveSheet().getRange(1, 1).getValue();
const rateTab = ss.getSheetByName("Rates");
const rates = rateTab.getDataRange().getValues();
const currentRow = ss.getActiveSheet().getActiveCell().getRow();
var rateToSearch = ss.getActiveSheet().getRange(currentRow, 1).getValue();
const rr = rates.findIndex((rate) => rate === rateToSearch);
const [firstRates] = rates;
const cc = firstRates.findIndex((rate) => rate === accountName);
return rates[rr][cc];
};
Note that the "vlookup" is not defined error indicates there is no vlookup variable / function declaration in scope. Which obviously is the case as there is no built-in Google Apps Script vlookup function.
You can't access random ranges from a custom function so you would have to provide the data to the function, some of the other solutions here that use get active spreadsheet won't work as a custom function which I am guessing is what the OP is looking for, here is an example of a script that does that but word of warning before you go down this road, custom functions are much slower than the built in functions so doing this will be much slower than vlookup and match, if you only have a few functions like this in the sheet you will be fine, but if you build large tables with dozens of rows that use custom functions it will slow down you spreadsheet substantially.
// Combines VLOOKUP and MATCH into a single function
// equivalent to VLOOKUP(rowValue, tableData, MATCH(columnName, tableHeader))
// but in this version tableData includes tableHeader
function findInTable(tableData, columnName, rowValue) {
if (rowValue === "") {
return "";
}
if (tableData.length == 0) {
return "Empty Table";
}
const header = tableData[0];
const index = header.indexOf(columnName);
if (index == -1) {
return `Can't find columnName: ${columnName}`;
}
const row = tableData.find(row => row[0] == rowValue);
if (row === undefined) {
return `Can't find row for rowValue: ${rowValue}`;
}
return row[index];
}
Another optimization I suggest you do is use named ranges, it allows you to transform something like:
=VLOOKUP(C5, 'Other Sheet'!A2:G782, MATCH("Column Name", 'Other Sheet'!A1:G1))
into a more readable and easier to look at:
=VLOOKUP(C5, someTableData, MATCH("Column Name", someTableHeader))
for the custom function form this will look like:
=findInTable(A1:G782, "Column Name", C5)
Note that I shorted the argument list by merging the data and header, this makes some assumptions about the table structure, e.g. that there is a one header line and that the lookup value is in the first column but it makes it even shorter and easier to read.
But as mention before this comes at the cost of being slower.
I ended up giving up on using this for my needs due to how slow it is and how much faster VLOOKUP and MATCH are since they are built in functions.
vlookup is not something you can use in a function in a script, it is a spreadsheet formula.
Google Scripts use JavaScript, so you'll need to write your code in JS then output it to a relevant cell.
If you could share your sheet/script we could help figure it out with you.
I have a spreadsheet in which I developed a custom function called RawVal:
function RawVal(BlockName) {
try {
var rawVal = 1;
var thiSheet = SpreadsheetApp.getActiveSheet();
var BlockRow = thiSheet.getRange("C:C").getValues().map(function(row) {return row[0];}).indexOf(BlockName);
if (BlockRow > -1) {
var baseVal = thiSheet.getRange("B" + (BlockRow+1)).getValue();
var ingVal = thiSheet.getRange("D" + (BlockRow+1)).getValue();
rawVal = Math.max(baseVal, ingVal);
Logger.log(BlockName+": base="+baseVal+"; ing="+ingVal+"; max="+rawVal);
}
return rawVal;
}
catch (e) {
Logger.log('RawVal yielded an error for " + Blockname + ": ' + e);
}
}
While the function is long, the intent is to replace a moderately sized function from having to be typed in on each row such as:
=if(sumif(C:C,"Emerald Block",D:D)=0,sumif(C:C,"Emerald Block",B:B),sumif(C:C,"Emerald Block",D:D))
The problem is sometimes it works and sometimes it just doesn't. And it doesn't seem to be related to the content. A cell that worked previously may display #NUM and have the error "Result was not a number". But if I delete it and retype it (but oddly not paste the formula), most of the time it will calculate correctly. Note: it is NOT stuck at "Loading", it is actually throwing an error.
Debug logs haven't been useful - and the inconsistency is driving me crazy. What have I done wrong?
EDIT: I replaced the instances of console.log with Logger.log - the cells calculated correctly for 6 hours and now have the #NUM error again.
It seems that your custom function is used in many places (on each row of the sheet). This and the fact that they stop working after a while points to excessive computational time that Google eventually refuse to provide. Try to follow their optimization suggestion and replace multiple custom functions with one function that processes an array and returns an array. Here is how it could work:
function RawVal(array) {
var thiSheet = SpreadsheetApp.getActiveSheet();
var valuesC = thiSheet.getRange("C:C").getValues().map(function(row) {return row[0];});
var valuesBD = thiSheet.getRange("B:D").getValues();
var output = array.map(function(row) {
var rawVal = 1;
var blockRow = valuesC.indexOf(row[0]);
if (blockRow > -1) {
var baseVal = valuesBD[blockRow][0];
var ingVal = valuesBD[blockRow][2];
rawVal = Math.max(baseVal, ingVal);
}
return [rawVal];
}
return output;
}
You'd use this function as =RawVal(E2:E100), for example. The argument is passed as a double array of values, and the output is a double array too.
Also, when using ranges like "C:C", consider whether the sheet has a lot of empty rows under the data: it's not unusual to see a sheet with 20000 empty rows that pointlessly get searched over by functions like that.
Alternative: use built-in functions
It seems that your custom function is mostly a re-implementation of existing =vlookup. For example,
=arrayformula(iferror(vlookup(H:H, {C:C, B:B}, 2, false), 1))
looks up all entries in H in column C, and returns the corresponding values in column B; one formula does this for all rows (and it returns 1 when there is no match). You could have another such for column D, and then another arrayformula to take elementwise maximum of those two columns (see Take element-wise maximum of two columns with an array formula for the latter). The intermediate columns can be hidden from the view.
I am trying to write a formula or script that would take two inputs, a student's Attendance and GPA and spit out their On-Track rating as described here.
My Google Sheet
function ONTRACK(Att, GPA){
function getAttendanceRow(number){
if(number>=98){row="A";}
else if(number>=95){row="B";}
else if(number>=90){row="C";}
else if(number>=80){row="D";}
else {row="E";}
return row
}
function getGPACol(number){
if(number<1){col="F";}
else if(number<2){col="G";}
else if(number<3){col="H";}
else {col="I";}
return col
}
var matrix=getAttendanceRow(Att) + getGPACol(GPA)
var matrix_hash={'AI':5, 'BI':5,
'AH':4, 'CI':4,
'AG':3, 'BG':3, 'BH':3, 'CH':3, 'DI':3,
'BF':2, 'CF':2, 'CG':2, 'DG':2, 'DH':2,'EH':2,
'DF':1, 'EF':1, 'EG':1 }
return matrix_hash[matrix]
}
TWO QUESTIONS
1. How do I apply this across large amount of rows without getting the timeout error?
I've tried to use setFormula
function makeN(){
ss.getRange("N2").setFormula("=ONTRACK(G2*100,H2)");
ss.getRange("N2").copyTo(ss.getRange("N2:N"+lastRow));
}
I've also played around with the map method as suggested my the documentation, but get an error about the first element being undefined. I'm not familiar with the map method, so my problem could lie there.
function ONTRACK2(input){
if (input.map) { // Test whether input is an array.
return input.map(ONTRACK2); // Recurse over array if so.
} else {
// do actual function work here
return ONTRACK(input[0][0]*100, input[0][1])
}
};
Something similar works for GPA, where I'm calculating GPA without an error:
function GPA2(input){
if (input.map) { // Test whether input is an array.
return input.map(GPA2); // Recurse over array if so.
} else {
// do actual function work here
return myAverage(getPoints(input[0][0]), getPoints(input[0][1]), getPoints(input[0][2]), getPoints(input[0][4]))
}
};
The other script-based idea would be to build some kind of array object through iteration that stores the "On Track" values and then writes them to the correct column.
2. Can this be done without AppScripts, as a in-Sheets formula instead?
I was playing around with the fuzzy lookups referenced here
I made a Reference Table
These are the formulas I've tried, where Columns G and H are my Attendance and GPA respectively.
=INDEX('Reference Table'!F2:F20,MATCH(2,INDEX(1/(('Reference Table'!D2:D20=G2)*('Reference Table'!E2:E20<=H2)),0)))
=ArrayFormula(INDEX('Reference Table'!$F$2:$F$20,MAX(ROW('Reference Table'!$D$2:$D$20)*(('Reference Table'!$D$2:$D$20)=G2)*(('Reference Table'!$E$2:$E$20)<=H16))))
Im not sure about the sheet formula, but you can do this with apps script.
Go to your sheet, on the menu, click "Find students Track" and "Calculate track". Column "P" will fill with students track data. Voila!
This is my function
function FindMyTrack()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var student = ss.getSheetByName("StudentData");//select sheets
var refer = ss.getSheetByName("Reference Table");//select sheets
var stData = student.getRange(2, 7, student.getLastRow(), 2).getValues();// get sheet data
var refData = refer.getRange('D2:F20').getValues();// get sheet data
var tracks = [];
//checking each student attendance and GPA with reference data
for(var n in stData ){
for(var p in refData){
if( stData[n][0] >= refData[p][0] && stData[n][1] >= refData[p][1]){
tracks.push([refData[p][2]]);//save matched value in an array
break;
}
}
}
// setting saved track data in the "P" (16) column
student.getRange(2, 16, tracks.length, 1).setValues(tracks);
}
You can check your sheets' associated code ("GPA") for alternations.
I don't have much experience using Javascript but I'm developing a simple code to filter some information relevant to a professor I'm helping. I am searching the row number of a certain amount of data using a for and then I'm using an array to store all the rows that contain those words. Since I'm using Appscript, I only need to relocate a certain amount of data from the row I'm returning to a final row I've already know. My code is as follows:
if(cell === "Average")
{
index++;
initialcoords[index] = n; // n is the iteration variable in the for
}
I've tested the contents of the array and they are just fine, so I'm storing correctly the rows. The problem is that I'm using a different method to paste the data in a different sheet in Google Spreadhsheets. My code to do so is the following:
function pasteInfo()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheetByName("Sheet 1");
var destination = ss.getSheetByName("Sheet 2");
var range = source.getRange(initialcoords[1], 1, 8, 3);
range.copyValuesToRange(destination, 4, 6, 4, 6);
}
My probelm is the getRange() since it prints an error like this:
can't find method getRange((class),number,number,number).
I believe that even if n is declared as an integer, the values that I'm returning are of a different type incompatible with the getRange() method. Could anyone help me to confirm this and to help me convert it to integer? I would really appreciate your help.
You first need to define the Sheet you want to get the data from since a Spreadsheet can have multiple Sheets.
You need to ensure you have appropriate default values defined before using the parameters, otherwise the interpreter will start making guess.
Provide defaults if parameters are empty:
function fillLine(row, column, length, bgcolor)
{
row = row || 0;
column = column || 0;
length = length || 1;
bgcolor = bgcolor || "red";
var sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange(1+row, 1+column, 1, length).setBackground(bgcolor)
}
You may also try the solution offered by community: Can't get Google Scripts working
This problem seems so simple but I've been searching/trying for several hours to find a solution. I basically have a large spreadsheet (3k rows of ISBN's) which each have a corresponding author, title, genre etc.
My plan was to make a function that allows me to simply produce a small UI, which takes in an Isbn and an amount, has a submit button (all done) and then finds that ISBN and increments a value in that row. The big stumbling block is being able to use the isbn which has been stored as a variable and finding the match in the table, as I simply cannot find some sort of "getValue()" function in google sheets.
I've seen several examples of people loading their entire data into an array and comparing the desired value to each individual entry, but that seems ridiculously inefficient and slow to me? Surely there must be a simple, efficient way that I'm just overlooking. All I'm after is a simple way of searching for a value, finding that value in the sheet and returning that row.
Thanks in advance!
Your assumption that there must be a more efficient way to find an item in a spreadsheet is not true. There is no "find" method in spreadsheet service but array iteration is fast and bulk data reading is also very fast so you shouldn't be annoyed with the speed parameter even for very long lists.
Here are 2 code snippets that find the row number of a string in column 1.
In the first one I transposed the array so that I could search in a single column in a simple array.
Both show the time it takes in milliseconds.
function testISBN1(){
var time = new Date().getTime();
var isbn = 'test1990'; // just as a test... create your own Ui to enter the value to search for
Logger.log(findISBN(isbn)+' in '+(new Date().getTime()-time)+' mSec');// view result... false if no occurrence (do something with that value)
}
function findISBN(isbn){
var sh = SpreadsheetApp.getActive().getSheetByName('Sheet1');// select your sheet
var data = sh.getDataRange().getValues(); // get all data at once
var isbnColumn = transpose(data)[0];// if isbn is column 1 (it became row1 because of transpose
for(var n in isbnColumn){
if(isbnColumn[n] == isbn){ n++ ; return n }; // if found, increment to get the sheet row number
}
return false;// not found
}
function transpose(a) { // classical Array transpose JS function
return Object.keys(a[0]).map(
function (c) { return a.map(function (r) { return r[c]; }); }
)
}
And the second one, without transpose takes about the same time to execute (about 230 mS in a 2000 rows sheet)
function testISBN2(){
var time = new Date().getTime();
var isbn = 'test1990'; // just as a test... create your own Ui to enter the value to search for
Logger.log(findISBN2(isbn)+' in '+(new Date().getTime()-time)+' mSec');// view result... false if no occurrence (do something with that value)
}
function findISBN2(isbn){
var sh = SpreadsheetApp.getActive().getSheetByName('Sheet1');// select your sheet
var data = sh.getDataRange().getValues(); // get all data at once
for(var n in data){
if(data[n][0] == isbn){ n++ ; return n }; // if found (in column 1), increment to get the sheet row number
}
return false;// not found
}
Below is the execution transcript that shows the time taken by every line of code. As you can see, the total execution time is mainly used to get the sheet and get its values.