I have a spreadsheet for soccer with an overview sheet (it's the first sheet) and 34 more sheets (for every matchday, called "x. ST") filled with values. In the overview sheet I want to get the sum of specific values from a set number of matchdays. I tried to realize this by an App Script. var start is the value that defines the first matchday, var end the last matchday. I want it as dynamic as possible so I can get values i.e. from Matchday 10 to 20 or 20 to 25 and so on.
Every sheet from start to end has a value in a given cell. This value can be "3" (for win), "1" (for draw) or "0" (for loss). Because this function should display the number of matchdays with wins, every "3" counts as 1, every "1" and "0" as 0. Logically the sum of all 1's is the number of the won matchdays. Here's the code:
function win(cell) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var sum = 0;
var end = ss.getActiveSheet().getRange(21, 2).getValue() + 1;
var start = ss.getActiveSheet().getRange(21, 3).getValue() + 1;
for (var i = start; i < end ; i++ ) {
var sheet = sheets[i];
var val = sheet.getRange(cell).getValue();
if (val == "3") {
val = 1;
} else {
val = 0;
}
sum += val;
}
return sum;
}
Now, my problem is that the code does work, but the sum is always a 0 (zero), regardless of the number of sheets with "3" in the given cell. I really don't see where's the problem. Anyone with an idea?
EDIT: Here's the Spreadsheet. I'm not sure it will help, but whatever:
https://docs.google.com/spreadsheets/d/1SoE4cJVAeeurmB7bvvGENEWvu2Mg1xvA3B3hTobBxW8/edit#gid=0
EDIT2: Ok guys, there were simple errors and the code works now. First I had to change cell in getRange(), and then I had to guarantee every value is an integer. There was also an error with start and end, causing the code searching in the wrong sheets. Because this sheets are currently without any values, there was always the sum of 0. Well, here's the current code:
function win(cell) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var sum = 0;
var end = ss.getActiveSheet().getRange(21, 2).getValue() + 1;
var start = ss.getActiveSheet().getRange(21, 3).getValue();
for (var i = start; i < end ; i++ ) {
var sheet = sheets[i];
var val = sheet.getRange([cell]).getValue();
parseInt(val) * 1;
if (val == 3) {
val = 1;
} else {
val = 0;
}
sum += val;
}
return sum;
}
I cannot be sure without looking at your spreadsheet, but just from looking at your code, I am guessing that you have the "Start" and "end" cells mixed up. The way it is now, both cells are in the same row, but end comes before start, which is not what you seemed to describe above.
Related
I am copying data from a spreadsheet titled after the specific month and placing it in my main spreadsheet. I have successfully copied the data into range K80:K94 on my Daily Hub sheet.
In range K80:K94 I now want to add a checkbox in column M if there is a value in column K. For example if there is a value in K80 and K81 there would be a checkbox in M80 and M81. I feel like this should be fairly straightforward, however I have tried a few different options including using IsBlank() and nothing seems to be working.
function dailyhubhabits() {
var montha = new Array(12);
montha[0] = "JANUARY";
montha[1] = "FEBRUARY";
montha[2] = "MARCH";
montha[3] = "APRIL";
montha[4] = "MAY";
montha[5] = "JUNE";
montha[6] = "JULY";
montha[7] = "AUGUST";
montha[8] = "SEPTEMBER";
montha[9] = "OCTOBER";
montha[10] = "NOVEMBER";
montha[11] = "DECEMBER";
var dailyhabitshubmonth = new Date();
var getdhmonth = montha[dailyhabitshubmonth.getMonth()];
Logger.log(getdhmonth);
var mhs = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(getdhmonth);
var monthhabitsogdata = mhs.getRange("C56:E70");
var gethabits = monthhabitsogdata.getValues();
Logger.log(gethabits);
var dhs = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("DAILY HUB");
var habitsarea = dhs.getRange("K80:K94");
monthhabitsogdata.copyTo(habitsarea);
//THIS IS WHERE I AM HAVING TROUBLE
var datavalues = dhs.getRange("K80:K94").getValues();
var data_leng = datavalues.length;
for(var i=0; i<data_leng; i++) {
if(datavalues[i][0].length != 0) {
dhs.getRange(i+1,14).insertCheckboxes();
}
}
}
You want to insert a checkbox on Column M when there is a value in the same row of column K.
There are two problems with this part of your script:
evaluating whether the cell has a value
defining the target range for the checkbox
Does the cell have a value?
length returns the number of records in an array, but it is not a good method for determining whether a cell contains a value. This is a popular topic; you might care to read Google Spreadheets Scripts: check if cell is empty for several methods.
a better approach is !== ""
Defining the target cell
dhs.getRange(i+1,14).insertCheckboxes(); - there are two problems here
Column M is 13
i starts at zero, so the first range value would be .getRange(1,14) = Cell N1.
so you need a variable that defines the startRow, such as:
var startRow = 80
REPLACE
//THIS IS WHERE I AM HAVING TROUBLE
var datavalues = dhs.getRange("K80:K94").getValues();
var data_leng = datavalues.length;
for(var i=0; i<data_leng; i++) {
if(datavalues[i][0].length != 0) {
dhs.getRange(i+1,14).insertCheckboxes();
}
}
WITH
var startRow = 80
var endRow = 94
var datavalues = dhs.getRange("K"+startRow+":K"+endRow).getValues()
var data_leng = datavalues.length;
for(var i=0; i<data_leng; i++) {
if(datavalues[i][0] !=="") {
dhs.getRange(i+startRow,13).insertCheckboxes()
}
}
SUGGESTION
In my understanding, here's your goal:
Check values in K80:K94
Insert a checkbox on a row in M that is adjacent to a row that isn't empty in the K80:K94 range.
Perhaps you could try this sample script to replace your current line on the section in inserting the check-boxes:
/** SUGGESTION
* 1. Iterate through the values in range K80:K94 & identify which aren't empty.
* 2. Get each non-empty values' row numbers.
* 3. To reduce runtime execution in the loop, if there are consecutive non-empty values, set them as a range (e.g. M80:M81). Otherwise a single value will be set as a single range (e.g. M83);
* 4. Iterate through these ranges & insert the checkboxes.
*/
var range = SpreadsheetApp.getActive().getRange('K80:K94');
var temp_values = range.getValues().map((x, i) => x != '' ? [x, (range.getLastRow() - (range.getNumRows() - i) + 1)].flat() : '*');
var ranges = temp_values.join().split('*').map(y => (y.replace(/[a-zA-Z,]+/g, '-')).split('-').filter(x => x != ''));
ranges.map(z => [...new Set([z[0], z[z.length - 1]])]).forEach(
row => row.length > 1 ? SpreadsheetApp.getActive().getRange(`M${row[0]}:M${row[1]}`).insertCheckboxes() :
SpreadsheetApp.getActive().getRange(`M${row[0]}`).insertCheckboxes()
);
/** End */
This sample script runs faster vs your current implementation as it shortens the data to be processed in the loop
Demo
Sample sheet
After running the script
I have a model in Google Sheets that is set up with one column per day. It contains both actuals and forecasts, and every day I need to roll forward formulas to replace forecasts with actuals. I can't roll forward the whole column, only a segment of it (there are reference numbers above and below that shouldn't be changed).
I have tried to write a script to do this for me every day, but I don't know how to make getRange reference a dynamic range. This is my attempt:
function rollColumn() {
var ss2 = SpreadsheetApp.openById('<ID redacted>');
ss2.getRange("=index(Model!$7:$7,,match(today()-2,Model!$4:$4,0)):index(Model!$168:$168,,match(today()-2,Model!$4:$4,0))").copyTo(ss2.getRange("=index(Model!$7:$7,,match(today()-1,Model!$4:$4,0)):index(Model!$168:$168,,match(today()-1,Model!$4:$4,0))"))
};
The INDEX formulas work insofar as they reference the relevant ranges (I have tested them in the spreadsheet). But clearly getRange doesn't accept formulas as an input. It also seems that Google Sheets doesn't allow for a named range to be created with formulas (which is how I would solve this in Excel).
Can someone help me recreate this functionality with GAS?
This is the closest existing question I've found on Stack Overflow, but I haven't been able to make it work:
Google Apps Script performing Index & Match function between two separate Google Sheets
Thank you!
You should add {contentsOnly:false} parameter to your code. something like this:
TemplateSheet.getRange("S2:T2").copyTo(DestSheet.getRange("S2:T"+LRow2+""), {contentsOnly:false});
Getting a date from column's title, then pasting formulas to the row to the right:
// note: we assume that sheet is disposed as in the following document: https://docs.google.com/spreadsheets/d/1BU2rhAZGOLYgzgSAdEz4fJkxEcPRpwl_TZ1SR5F0y08/edit?ts=5a32fcc5#gid=0
function find_3formulas() {
var sheet = SpreadsheetApp.getActiveSheet(),
leftTitle, // this variable will stay unused because we do not need a vertical index
topTitle = todayMinus_xDays(2),
topTitlesRange = sheet.getRange("G3:T3"),
leftTitlesRange = sheet.getRange("A4:A8"); // this range will stay unused.
var coor = findCoordinates(leftTitlesRange, leftTitle, topTitlesRange, topTitle);
if (coor.row == null || coor.column == null) {
sheet.getRange("M12:M14").setFormula('="NULL: please check logs"');
return;
}
var rowAxis = 4 + coor.row;
var colAxis = 8 + coor.column;
var fromRange = sheet.getRange(rowAxis, colAxis, 3, 1);
var toRange = sheet.getRange(rowAxis, colAxis + 1, 3, 1);
Logger.log(fromRange.getA1Notation())
Logger.log(toRange.getA1Notation());
var threeFormulas = fromRange.getFormulas();
toRange.setFormulas(threeFormulas)
}
// unused in current script!
function findCoordinates(leftTitlesRange, leftTitle, topTitlesRange, topTitle) {
var formattedDate,
row = 0,
column = 0;
if (leftTitle) {
row = findRow(leftTitlesRange, leftTitle);
}
if (topTitle) {
column = findColumn(topTitlesRange, topTitle);
}
var array = {row:row, column:column}
return array;
}
// unused in current script!
function findRow(range, valueToSearch) {
var colRows = range.getValues();
for (i = 0; i < colRows.length; i++) {
if (valueToSearch == colRows[i][0]) {return i;}
}
// however, if found nothing:
Logger.log("the value " + valueToSearch + " could not be found in row titles");
return null;
}
// assumes that column titles are dates, therefore of type object.
function findColumn(range, valueToSearch) {
var colTitles = range.getValues();
for (i = 0; i < colTitles[0].length; i++) {
if (typeof colTitles[0][i] == "object") {
formattedDate = Utilities.formatDate(colTitles[0][i], "GMT", "yyyy-MM-dd")
};
if (valueToSearch === formattedDate) {return i;}
}
// however, if found nothing:
Logger.log("today's date, " + valueToSearch + ", could not be found in column titles");
return null;
}
// substracts 2 days from today, then returns the result in string format.
function todayMinus_xDays(x) {
var d = new Date();
d = new Date(d - x * 24 * 60 * 60 * 1000);
d = Utilities.formatDate(d, "GMT", "yyyy-MM-dd");
return d;
}
I'm trying to create a custom function for a google sheet that will find the rightmost string in a 1d range of cells, then return a header (in a specified row).
Here's where I'm stuck. I can get the string for that cell with the following code:
function FarRightHeader(range, rownumber) {
var cells = range[0].length;//gets the number of cells
for (var i = 0; i < cells; i++) { //loop through the cells in the range
var j = cells - 1 - i; // j will start at the end so the loop can work from left to right
if (range[0][j] != "") { //if the cell contains something
break; //jump out of the loop
}
}
var activeCell = SpreadsheetApp.getActiveRange().getA1Notation();
var activeColumn = activeCell.charAt(0);
var FarRightCell = "Hi, I'm___" + range[0][j] + "___ and I'm in column " + activeColumn;
return FarRightCell;
}
here's the glitch - the activeCell variable is taking the cell from which the custom function is called, not the far right populated cell in the range. I understand why this is happening, but don't know how to get the column I want.
To me it appears that the function is treating the range as simply the values in the cells divorced from what cells they actually are in the spreadsheet.
Is there a way to get information about the range within the spreadsheet that the function takes as a parameter?
Thanks in advance for any help or leads you can give me!
I see no glitch, you're imagining your loop as searching the cells, while you're just searching an Array that you got from the cells values.
But as your code sugests you don't need to retrieve the column like, you already have it, saved in the j, you just need to convert it to a letter, here's a code I fetched:
function colName(n) {
var ordA = 'a'.charCodeAt(0);
var ordZ = 'z'.charCodeAt(0);
var len = ordZ - ordA + 1;
var s = "";
while(n >= 0) {
s = String.fromCharCode(n % len + ordA) + s;
n = Math.floor(n / len) - 1;
}
return s;
}
Also here's a suggested better for loop:
var column; // Initialize before so if doesn't become private scope
for (column = ( range[0].length - 1 ); column > 0; column--) {
if (range[0][j] != "")
break;
}
Note: This requires that the Range starts at "A" column, if it doesn't you have to add the range first column to column.
I'm trying to read a cell from sheet INPUT and check to see if it is present in sheet STORAGE (which has only one column). If it is found, it would need to be removed. The script can terminate if the string is found. I have written some code that seems like it would work in theory, but just times out after six minutes when I try using it.
I've tried setting it up so that the loops only iterate once each, but it still seems to be stuck somewhere. Here is my code:
var active_spreadsheet = SpreadsheetApp.getActive();
var sheet_input = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('INPUT');
var sheet_storage = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('STORAGE');
var last_row_counter = 0;
function findAndRemove() {
last_row_counter = sheet_storage.getLastRow();
var n = 1;
while (n <= last_row_counter) {
if (sheet_input.getRange("B2").getValue() == sheet_storage.getRange(n,1)) {
sheet_storage.deleteRow(n);
n = last_row_counter;
}
n = n++;
}
Brian,
See if this runs better:
function findAndRemove() {
var ss = SpreadsheetApp.getActive(),
valToFind = ss.getSheetByName('INPUT')
.getRange('B2')
.getValue(),
sheetToCheck = ss.getSheetByName('STORAGE'),
values = sheetToCheck.getRange('A2:A')
.getValues();
for (var i = 0, l = values.length; i < l; i++) {
if (values[i][0] == valToFind) {
sheetToCheck.deleteRow(i + 2);
break;
}
}
}
As you mentioned the code will exit after the first time the value is found. I used col A as the column that needs to be searched (change to suit). As you see this code gets all the values of that column in one call, then loops through it to see if a match is found.
Where do I start?!
I suggest you store the sheet_input.getRange("B2").getValue() in a variable before running the loop, as this means the script only needs to query the value once, instead of once per row.
In javascript n++ already means "increment n by 1" ie: n=n+1;, so the assignment is over the top. Just use: n++;
A for loop is better to use in this case as it includes the start number, the end condition and the increment all-in-one; Some info Here
The main problem is that when you do your comparison sheet_input.getRange("B2").getValue() == sheet_storage.getRange(n,1) you are comparing a value eg "hello" to a CELL, not its value. So you need sheet_storage.getRange(n,1).getValue() instead.
Instead of artificially saying "if a matching value is found, remove the row and then increment the row count to max" to stop the loop, just use the break; clause, which exits the loop.
I strongly suggest you provide some kind of prompt to let the user know when the function finished, and if it worked. See info on class Browser.msgBox()
Here's my code:
var active_spreadsheet = SpreadsheetApp.getActive();
var sheet_input = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('INPUT');
var sheet_storage = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('STORAGE');
function findAndRemove() {
var comapreValue = sheet_input.getRange("B2").getValue();
var last_row_counter = sheet_storage.getLastRow();
var sum_of_deleted_rows = 0;
for (var n = 1; n <= last_row_counter; n++) {
if (comapreValue == sheet_storage.getRange(n,1).getValue()) {
sheet_storage.deleteRow(n);
sum_of_deleted_rows++;
// break; //if you would like to stop after just one row is removed, un-comment this line
}
}
Browser.msgBox(sum_of_deleted_rows + " row(s) deleted");
}
i have a spreadsheet that i keep track of tasks i need to do, once complete i enter a date in the last column. What i want is for that completed task to be moved to sheet 2.
At present i have sheet 1 named SUD_schedule and i want the completed row of data to be moved to sheet 2 named SUD_archive. I've looked through the forum posts already and i've tried a variation of scripts but so far no luck. The closest i have come is this script:
function onEdit() {
var sheet1 = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();//Original sheet
var sheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheets()[1];//target sheet
// to act on only one sheet, check the sheet name here:
//If it si not first sheet, it will do nothing
if (sheet1.getSheetName() != "SUD_schedule") {
return;
}
//Get Row and column index of active cell.
var rowIndex = sheet1.getActiveRange().getRowIndex();
var colIndex = sheet1.getActiveRange().getColumnIndex();
//If the selected column is 10th and it is not a header row
if (colIndex == 16 && rowIndex > 1) {
//Get the data from the current row
var data = sheet1.getRange(rowIndex,1,1,9).getValues();
var lastRow2;
(sheet2.getLastRow()==0)?lastRow2=1:lastRow2=sheet2.getLastRow()+1;
//Copy the data to the lastRow+1th row in target sheet
sheet2.getRange(lastRow2,1,1,data[0].length).setValues(data);
}
}
Column P (16) is the task complete date, row 1 is frozen and contains column headers.
Can anybody help show where i'm going wrong.
Kind regards
Den
Your code is not generic and you are more complicating your objective. Below will work out your need.
function onEdit(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName('SUD_schedule');
var sheet2 = ss.getSheetByName('SUD_archive');
var dateColumn = "16";
var array = []
var range = sheet1.getRange(1, 1, sheet1.getLastRow(), dateColumn);
for (var i = 2; i <= sheet1.getLastRow(); i++) //i iterates from 2 as you say R1 is header
{
if(isValidDate(range.getCell(i, dateColumn).getValue()) == true) //checking if any values on column16 is valid date
{
data = sheet1.getRange(i, 1, 1, dateColumn).getValues(); //Getting the range values of particular row where C16 is date
for (var j = 0; j < dateColumn; j++) //Adding the row in array
{
array.push(data[0][j]);
}
}
if(array.length > 0)
{
sheet2.appendRow(array); //Appending the row in sheet2
array = [];
sheet1.deleteRow(i); //deleting the row in sheet as you said you want to move, if you copy remove this and next line
i=i-1; //managing i value after deleting a row.
}
}
}
//Below function return true if the given String is date, else false
function isValidDate(d) {
if ( Object.prototype.toString.call(d) !== "[object Date]" )
return false;
return !isNaN(d.getTime());
}
I am not sure that the syntax you have as used below is entirely correct.
(sheet2.getLastRow()==0)?lastRow2=1:lastRow2=sheet2.getLastRow()+1;
sheet2.getRange(lastRow2,1,1,data[0].length).setValues(data);
What I know will work for certain is if you omit the variable lastRow2 all together and use this instead.
sheet2.getRange(getLastRow+1,1,1,data[0].length).setValues(data);
To complement Joachin's answer, here is how you can adapt that code if you don't have the date in the last row. In the below shown part of the code replace Lastcolumnumber with your last column.
//Getting the range values of particular row where C16 is date
data = sheet1.getRange(i, 1, 1, LASTCOLUMNNUMBER).getValues();
//Adding the row in array
for (var j = 0; j < LASTCOLUMNNUMBER; j++)