I have a google sheet that I want to add a conditional formatting, but the merged cells kinda of get in the way.
In my spreadsheet, I have the columns B,C,D and E where i'm using a formula in column B for when columns C,D and E are "done" column B gets a green background. The problem is, for some rows, columns D and E are merged.
This is the formula I'm using in column B
=and(C:C="done",D:D="done")
My desired result is: When the columns D and E are not merged, column B only gets the green background if columns C,D and E are "done", or else, it stays blank.
When D and E are Merged : B only gets green background if C and DE are "done", or else, stays blank.
Thanks in advance!!
If E always has a non-empty value when D and E aren't merged, you can use that to check for when E is blank using ISBLANK(E:E):
=AND(C:C="done", D:D="done", OR(E:E="done", ISBLANK(E:E)))
Otherwise, there isn't a way to test if a cell is part of a merged range without using a custom function, which is far less efficient, but could technically work with the assistance of a new helper column (e.g. column Z):
/**
* #customfunction
*/
function ISMERGED(cellAddress) {
var cell = SpreadsheetApp.getActiveSheet().getRange(cellAddress);
return cell.isPartOfMerge();
}
Where column Z (or whatever you choose for your helper) has the following in every relevant row (assuming data starts in row 1):
=ISMERGED(ADDRESS(ROW(E1), COLUMN(E1)))
=ISMERGED(ADDRESS(ROW(E2), COLUMN(E2)))
=ISMERGED(ADDRESS(ROW(E3), COLUMN(E3)))
...and so on.
And then for formatting B, use:
=AND(C:C="done", D:D="done", OR(E:E="done", $Z:$Z))
WARNING: If you toggle between merging and unmerging, Z wont contain the correct values immediately, though, because custom functions only re-compute when input changes (and the address of the cell wont in this case).
Update
Here's how you could compute your helper for the whole column downward, by row, by just putting the value in the topmost cell (ex: =ISMERGED("E:E") in cell Z1):
/**
* #customfunction
*/
function ISMERGED(rangeAddress) {
var range = SpreadsheetApp.getActiveSheet().getRange(rangeAddress);
var numCols = range.getNumColumns();
var numRows = range.getNumRows();
var result = [];
for (var i=0; i < numRows; i++) {
var rowRange = range.offset(i, 0, 1, numCols);
result.push(rowRange.isPartOfMerge());
}
return result;
}
best you can get is:
=OR(AND(C:C="done", D:D="done", E:E="done"),
AND(C:C="done", D:D="done", ISBLANK(E:E)))
which will work unless 4th row:
but you could pre-populate the whole E column with =CHAR(1) and then:
Related
I am a beginner in google sheets and I couldn't get around this formula. I have range of cells and I want to subtract last non empty cell to first cell (Z-A), here is the image:
As the values are updated in columns C, D, E and so on. I want to get the last non empty cell (from right) and subtract the values by moving backward (left). Like this:
sub = 10(Column G)-0(Column F)-10(Column E)-0(Column D)-10(Column C)
Can we devise a formula which will get the last non empty cell and subtract values until the first value? Here is the link to the sample sheet Thank you
try:
=LOOKUP(1, INDEX(1/(C2:F2<>"")), C2:F2)-(SUM(C2:F2)-
LOOKUP(1, INDEX(1/(C2:F2<>"")), C2:F2))
Suggestion: Use a custom function
You may use the following script as a custom function to get the difference between the value of the last cell and the sum of the other cells:
function SUBTRACTFROMLASTNUMBER(data) { //you can rename the custom function name
var sum = 0;
var data2 = data[0].filter(x => {
return (x != "") ? x : null;
}); //filtered data
var lastNumber = data2.pop(); //last number
data2.map(x => sum += x); //sums the remaining values
return (lastNumber - sum); //returns the output
}
This custom function extracts the selected data from the sheet and then separates the value of the last cell using pop() and then filters and sums the remaining data using filter() and map() and then subtracts the sum from the value of the last cell.
Usage
You may use this function as:
=SUBTRACTFROMLASTNUMBER(<range>)
Reference:
How to Manipulate Arrays in JavaScript
I have a table of teams, names, and roles. I am trying to move that data to a separate table, but I want to write a query that returns the values in adjacent cells as opposed to directly underneath which I think is the default.
Here is a picture of the table
=QUERY(A2:C9, "select B where A = 'Bears' and C = 'Coach'", 0)
// returns coach name for team 'bears'
=QUERY(A2:C9, "select B where A = 'Bears' and C = 'Player'", 0)
// returns all player names for team 'bears'
The first query achieves what I want because there is only one coach on each team to return, but the second query does not achieve what I want which is to return the values sideways to so fill in the Player1-3 columns and not impede the row beneath.
delete everything in range E2:I. then...
paste in E2 cell:
=UNIQUE(A2:A)
paste in F2 cell and drag down:
=FILTER(B$2:B, A$2:A=E2, C$2:C="coach")
paste in G2 cell and drag down:
=TRANSPOSE(FILTER(B$2:B, A$2:A=E2, C$2:C="player"))
UPDATE:
=TRANSPOSE(QUERY(Players!A:I, "select I where G = 'player' and A = "&
QUERY(Teams!A:F, "select A where F = '"&C2&"'", 0), 0))
How my sheet works
I'm making a spreadsheet to show how much parts I have. By using a dropdown, am I able to show that I created a product. With conditional formatting I am showing that having 0 items isn't an issue when the product is created. Created products with 0 items change from red to purple. Purple means it doesn't matter to have 0 items from this product.
My issue
My issue starts with my dropdown. If I merge cells, The value will go into the upperleft cell. This means other cells inside the merged cell are blank. This gives me a problem with conditional formatting.
My conditional formatting code example:
=if($D2=0;$E2="Created")
I have to change this code for every cell because of the condition combined with a dropdown. Having more than 250 rows would be inhumanly hard to do by hand.
My questions
Are there ways to give all cells of a merged cell the value of the combined cell in an efficient way?
Is there a better way to make my conditional formatting code applyable to merged cells?
This is my sheet
Product items collected sheet link (Shows the problem and solution!)
Product items collected sheet image (Version 1)
Product items collected sheet image (Version 2)
At the heart of this question is the operation of merged cells. When a cell is merged, say over several rows, only the cell at the top left of the merged cell can contain data, respond to conditional formatting, and so on. In a manner of speaking the other cells cease to exist and values CANNOT be assign to them.
The questioner asks:
Q: Are there ways to give all cells of a merged cell the value of the combined cell in an efficient way?
A: No. Not just in an "efficient" way; it's just not possible.
Q: Is there a better way to make my conditional formatting code applicable to merged cells?
A: No and yes ;)
No. In so far as a merged cell is concerned, everything is driven by the value in the top cell of the merged range. There are no other options for the "rest" of the merged cell.
Yes. I'd create a "helper" cells in Column F as in this screenshot
The code to achieve this is dynamic - it will automatically adapt to adding more products, more items, etc.
The logic is fairly simple: Start in F2, test whether E2 has a value (that is, is it the top of the merged cell?). If yes, then assign the value of E2 to F2 AND put that value in a variable for the following cells. If no, the cell in Column E must be part of a merged cell, so assign the value for Column F to the variable that was saved earlier.
function so5270705902() {
// basic declarations
var ss = SpreadsheetApp.getActiveSpreadsheet();
// note this is going to work on the second sheet in the spreadsheet - this can be edited.
var sheet = ss.getSheets()[1];
// Column B contains no merged cells, and always contains data (it is the BOM for the Products).
// so we'll use it to established the last row of data.
var Bvals = sheet.getRange("B1:B").getValues();
var Blast = Bvals.filter(String).length;
// Row 1 is a header row, so data commences in Row 2 - this can be edited
var dataStart = 2;
// Logger.log("the last row in column D = "+Blast);// DEBUG
// set up to loop through the rows of Column F
var mergedcellvalue = "";
for (i = dataStart; i < (Blast + 1); i++) {
// set the range for the row
var range = sheet.getRange(i, 6);
//Logger.log("row#"+i+" = "+range.getA1Notation()); DEBUG
// get the value in column E
var ECell = range.offset(0, -1);
var ECellVal = ECell.getValue();
//Logger.log("offsetrange#"+i+" range value = "+ECellVal);
//Logger.log("Column E, row#"+i+", value = "+ECell.getA1Notation()+" range value = "+ECellVal);//DEBUG
// when a row is merged, on the top row contains any data
// so we'll evaluate to see whether there is any value in this row in Column E
if (ECell.isBlank()) {
//Logger.log("ECell is blank. We're in the middle of the Merged Cell"); ??DEBUG
// Set the value to the lastes value of "mergedcellvalue"
range.setValue(mergedcellvalue);
} else {
//Logger.log("ECell has a value. We're at the top of the merged cell");//DEBUG
// paste the ECellVal into this range
range.setValue(ECellVal);
// Update the "mergedcellvalue" variable so that it can be applied against lower cells of this merged cell
mergedcellvalue = ECellVal;
} // end of the if isblank
} // end of the loop through column F
}
UPDATE 22 October 2018
For development purposes, I used a small range of only 14 rows in Column E. However the questioner's data covers over 250 rows, so I expanded development testing to cover 336 rows (yeah, I know, but I was copy/pasting and I ended up with 336 and was too lazy to delete any rows. OK?). I found that the code took over 81 seconds to process. Not good.
The primary reason (about 80 seconds worth) for the long processing time is that there is a getValue statement within the loop - var ECellVal = ECell.getValue();. This costs about 0.2 seconds per instance. Including getValue in a loop is a classic performance mistake. My bad. So I modified the code to get the values of Column E BEFORE the loop
var Evals = sheet.getRange("e2:E").getValues();.
I was surprised when the execution time stayed around the same mark. The reason was that the isBlank evaluation - if (ECell.isBlank()) { which previously took no time at all, was now consuming #0.2 second per instance. Not good++. So after searching Stack Overflow, I modified this line as follows:
if (!Evals[(i-dataStart)][0]) {.
Including setValues in a loop is also asking for trouble. An option would have been to write the values to an array and then, after the loop, update the Column E values with the array. However in this case, the execution time doesn't seem to have suffered and I'm leaving the setValues inside the loop.
With these two changes, total execution time is now 1.158 seconds. That's a percentage reduction of , um, a LOT.
function so5270705903() {
// basic declarations
var ss = SpreadsheetApp.getActiveSpreadsheet();
// note this is going to work on the second sheet in the spreadsheet - this can be edited.
var sheet = ss.getSheets()[2];
// Column B contains no merged cells, and always contains data (it is the BOM for the Products).
// so we'll use it to established the last row of data.
var Bvals = sheet.getRange("B1:B").getValues();
var Blast = Bvals.filter(String).length;
// Row 1 is a header row, so data commences in Row 2 - this can be edited
var dataStart = 2;
// Logger.log("the last row in column D = "+Blast);// DEBUG
// set up to loop through the rows of Column F
var mergedcellvalue = "";
// get the values for Column E BEFORE the loop
var Evals = sheet.getRange("e2:E").getValues();
for (i = dataStart; i < (Blast + 1); i++) {
// set the range for the row
var range = sheet.getRange(i, 6);
//Logger.log("row#"+i+" = "+range.getA1Notation()); DEBUG
// get the value in column E
var ECell = range.offset(0, -1);
var ECellVal = Evals[(i - dataStart)][0];
//Logger.log("Column E, row#"+i+", value = "+ECell.getA1Notation()+" range value = "+ECellVal);//DEBU
// when a row is merged, on the top row contains any data
// so we'll evaluate to see whether there is any value in this row in Column E
// instead is isblank, which was talking 0.2 seconds to evaluate, this if is more simple
if (!Evals[(i - dataStart)][0]) {
//Logger.log("ECell is blank. We're in the middle of the Merged Cell"); //DEBUG
// Set the value to the lastes value of "mergedcellvalue"
range.setValue(mergedcellvalue);
} else {
//Logger.log("ECell has a value. We're at the top of the merged cell");//DEBUG
// paste the ECellVal into this range
range.setValue(ECellVal);
// Update the "mergedcellvalue" variable so that it can be applied against lower cells of this merged cell
mergedcellvalue = ECellVal;
} // end of the if isblank
} // end of the loop through column F
}
UPDATE 3 March 2019
The questioner made his final changes to the code. This code is the final solution.
function reloadCreatedCells() {
// Basic declarations.
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Note this is going to work on the second sheet in the spreadsheet - this can be edited.
var sheet = ss.getSheets()[1];
// Column B contains no merged cells, and always contains data (it is the BOM for the Products).
// so we'll use it to established the last row of data.
var D_vals = sheet.getRange("D1:D").getValues();
var D_last = D_vals.filter(String).length;
// First row with data.
var dataStart = 2;
// Set up to loop through the rows of Column H - K.
var mergedcellvalue = "";
// Get the values for Column H - K BEFORE the loop.
var H_K_vals = sheet.getRange("H2:K").getValues();
// How many people we have.
var people = 4;
// The first vertical row.
var rowStart = 12;
// Horizontal rows.
for (var h = 0; h < people; h++) {
// Vertical rows.
for (var v = dataStart; v < D_last; v++) {
// Set the range for the row.
var range = sheet.getRange(v, rowStart + h);
// Logger.log(range.getA1Notation()); //DEBUG
// Get the value in column H - K.
var H_K_Cell = range.offset(0, -people);
// Adding Created and not created values inside L - O.
var H_K_CellVal = H_K_vals[(v - dataStart)][h];
// Logger.log(H_K_Cell.getA1Notation() + ': ' + H_K_CellVal); //DEBUG
// When a row is merged, the value is only inside the top row.
// Therefore, you need to check if the value is empty or not.
// If the value is empty. Place the top value of the merged cell inside the empty cell.
if (!H_K_vals[(v - dataStart)][h]) {
// Logger.log(H_K_Cell.getA1Notation() + ": is blank. We're below the top cell of the merged cell."); //DEBUG
// Set the value to the top cell of the merged cell with "mergedcellvalue".
range.setValue(mergedcellvalue);
} else {
// Logger.log(H_K_Cell.getA1Notation() + ": has a value. We're at the top of the merged cell."); //DEBUG
// Paste the H_K_CellVal into this range.
range.setValue(H_K_CellVal);
// Update the "mergedcellvalue" variable, so that it can be applied against lower cells of this merged cell.
mergedcellvalue = H_K_CellVal;
} // end of the if isblank.
} // End of the vertical row loop.
} // End of the horizontal row loop.
}
I am new to google scripts. I have a range where the cells in a column change. If the number of a cell from that column is above zero then the script has to send an email with the message being the content from a cell in the same row with the updated cell(that is, different column) Let's say If a value in column I changes then automatically it has to send an email with the message contained in the same row but column F for example. Please see below, I would appreciate some help.
function SendValue() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName("Dividend");
var values = sheet.getRange("I2:I").getValues();
//var values1 = sheet.getRange("G2:G").getValues();
var results = [];
for(var i=0;i<values.length;i++){
if(values[i]>0){
results.push(sheet.getIndex([+i+2]+"G"));
// +2 because the loop start at zero and first line is the second one (I2)
}
}
MailApp.sendEmail('sxxxxxx#gmail.com', 'EX-DIVIDEND', results );
};
getValues() returns a 2 dimensional array, or more accurately an array of arrays, even if one of the dimensions is 1. values[i] is not a value but rather an array representing the i row of the range. You will need to acess that array to get at the cell at a particular column in that row.
replace if(values[i]>0) with if(values[i][0]>0)
I suspect that results.push(sheet.getIndex([+i+2]+"G")); should be results.push(sheet.getRange("G"+(i+2)).getValue()); as getIndex() doesn't take arguments and that looks like it is meant to be A1 notation.
However there is a better option than trying to construct A1Notation.
getRange() supports using numbers to refer to rows and columns. getRange(7, i+2) is a better choice.
https://developers.google.com/apps-script/reference/spreadsheet/range#getvalues
https://developers.google.com/apps-script/reference/spreadsheet/sheet#getRange(Integer,Integer)
I am new at this so still trying to figure how everything works.
I have a sheet that collects responses from a Google Form. Based on the answer to one of those questions I would like the row from that sheet to move to a different sheet document all together (not a sheet in the same document).
I have it set on a time based trigger every minute so as new responses come in it would kick them over to the correct document and then delete the row from the original spreadsheet.
I have been able to get part of the way there. I would like for it to take the row, columns A through E, move those to the correct document, find where the next open row is and place the data in columns A through E on the new document.
Where my issue is coming in at the moment is when the row is moved to the new document. I have formulas saved in columns G - Z on the destination page. It is finding the last row with a formula and placing the row after that (which is at the very bottom of the page). I am pretty sure this has to do with using an array? But I may be wrong. Is there a way to just have that look at the destination page column A-E, find the next blank row, and copy A-E from the original file to the new page?
arr = [],
values = sheetOrg.getDataRange().getValues(),
i = values.length;
while (--i) {
if (value1ToWatch.indexOf(values[i][1]) > -1) {
arr.unshift(values[i])
sheetOrg.deleteRow(i + 1)
sheet1in.getRange(sheet1in.getLastRow()+1, 1, arr.length, arr[0].length).setValues(arr);
};
I have multiple If statements each with some changes to the "valueToWatch" and the "Sheet1in" for different values and destination pages. If that information helps at all.
You can find the last cell in a column with data in it like this:
function findLastValueInColumn() {
var column = "A"; // change to whatever column you want to check
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var lastDataRow = sheet.getDataRange().getLastRow();
for (var currentRow = lastDataRow; currentRow > 0; currentRow--) {
var cellName = column + currentRow;
var cellval = sheet.getRange( cellName ).getValue();
if (cellval != "") {
Logger.log("Last value in Column " + column + " is in cell " + currentRow);
break;
}
}
}
You can then use this function to figure out where to start your new data.