I often run into VALUE! errors in my calculations because they contain numbers and text. How can I leave the text in the cell and proceed with the calculation?
For example:
cell A1 contents look like this: 101.1 J
cell A2 contents look like this: 500 U
cell A3 contents look like this: 0.2
If I want to add A1+A2+A3 into cell A4, how can I ignore the J and U to calculate 101.1+500+0.2 to get 602.3 in cell A4?
Thanks!
You need extract the values from the strings - and this can only be done if you have some kind of information about the format to the numbers. In your example, you could place the following formula in B1:B3 and then add a =SUM(B1:B3):
=IF(ISNUMBER(A1),A1,VALUE(LEFT(A1,SEARCH(" ",A1)-1)))
The above formula will extract the number and convert it to a value - unless it was already a number.
Using Custom Function
Place below code in Standard Module
Function add_num(cell1, ParamArray Arr() As Variant)
Dim temp As Double
For i = LBound(Arr) To UBound(Arr)
temp = temp + GetNumber(Arr(i))
Next
add_num = GetNumber(cell1.Value) + temp
End Function
Function GetNumber(ByVal str As String) As Double
Dim objRegEx As Object
Set objRegEx = CreateObject("VBScript.RegExp")
objRegEx.IgnoreCase = True
objRegEx.Global = True
objRegEx.Pattern = "\d{1,2}([\.,][\d{1,2}])?"
Set allMatches = objRegEx.Execute(str)
For i = 0 To allMatches.Count - 1
result = result & allMatches.Item(i)
Next
GetNumber = result
End Function
add_num function can be called from excel interface using =addnum(<cells>). It accepts multiple cells.
Related
The Google Sheets API seems vague and I'm probably just too tired.
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var positives = sheet.getRange("D3:AG3");
var negatives = sheet.getRange("C4:C33");
for (i=0;i<positives.getLastColumn();i++) {
var j = positives[i]*-1;
negatives[i].setValue(j);
}
}
I'm sure I'm doing eight things wrong but if someone is more familiar with Google Sheets, please throw a brick at me.
First, positives is a ranges, and you need to use getValues() to get an array that you can manipulate.
Second, it's not recommended to use Sheets API methods inside loops, the best practice is to manipulate arrays in loops and then use single get and set values API to read / write to a range.
Sample Code:
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var positives = sheet.getRange("D3:AG3").getValues();
var negatives = sheet.getRange("C4:C33");
var result = [];
for (i = 0; i < positives[0].length; i++) {
result.push([positives[0][i] * -1]);
}
negatives.setValues(result);
}
Sample Output: (I only put values in three rows)
Reference:
push()
Avoid using onEdit for these kind of changes as it will be resource intensive. You are changing all the values of the column into negative of the row EVERY TIME you edit the sheet (Unless that should be the case)
If you really want to use onEdit, be sure to limit it only when the specific range is edited.
Code:
function onEdit(e) {
const row = e.range.getRow();
const column = e.range.getColumn();
// if edited range is within D3:AG3
if(row == 3 && column >= 4 && column <= 33) {
// write to the corresponding row (invert col and row)
e.source.getActiveSheet().getRange(column, row).setValue(e.value * -1);
}
}
Note:
Behaviour of the onEdit function is that when you edit the range D3:AG3, it will negate its value and write into its corresponding destination, one by one.
If you edit D3, it will assign that negative value into C4, nothing more.
If you edit outside the positive range, it will not do anything.
Another approach is to copy your positive row into negative column by transforming your data structure into the destination by bulk.
Code:
function rowToColumn() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var pRange = sheet.getRange("D3:AG3");
var pValues = pRange.getValues();
// pValues is a 2D array now
// row range values = [[1, 2, 3, ...]
var negatives = sheet.getRange("C4:C33");
// column range values = [[1], [2], [3], ...]
// since structure of row is different than column
// one thing we can do is convert the row into column structure
// and multiply each element with -1, then assign to negatives
pValues = pValues.map(function(item) {
item = item.map(function(col) {
return [col * -1];
});
return item;
})[0];
// set values into the negatives range
negatives.setValues(pValues);
}
Note:
Behaviour of the rowToColumn function is that it transfers all the values of the row range and then put it into negatives range all at once.
Blank cells will yield 0 by default, add a condition on return [col * -1]; if you want blank cells to return other values instead.
Output:
==================================
UPDATE 11 December 2019
My Question is more about Macro Script
The GOAL (in illustration)
to change below raw sheet:
to more readable format:
Basically what i'm doing is split the campaign name with the separator and parse it.
I don't have the problem if the function on only process single cell,for example:
on "Report" Sheet the CELL B2 , is taking data from "Data" B2 ONLY
i got problem when the return data require conditional operator that involve specific condition. So while processing cell B2, it require content from E2, D2, etc
=====================================
i'm taking data from Google Ads/Analytics API to Google Sheet on specific worksheet (i call it 'Raw Data').
Now i'm using pattern for the campaign, so i can easily split/break with separator in order for me to get specific data.
For Example:
With this, by using underscore as separator, i can split campaign name, into various data:
Campaign Objective: Sales
Campaign Title: TBMB
Network: SEM
Branch: All
Targeting: Keywords
..etc
Then i create new sheet called Called CReport which consist the same data from Raw Data sheet, but in much better visualization for marketing people.
Now, after searching on Google, i found the solution for self reference cell.
The script goes like this:
function getSegment(data,index){
temp=data.split("_");
return temp[index-1];
}
function dataParse(input,dataSegment){
return Array.isArray(input) ? input.map(function(e){
return e.map(function(f){
if(f!=""){
return getSegment(f,dataSegment);
}
}
)}
) : "false usage";
}
So if i want to have a column with Network Name, i can place this formula on row 2 (because row 1 is for table header) something like this:
=ArrayFormula(dataParse('RAW DATA'!B2:B;2))
Now my question:
This works for self-reference cell, means if the data taken from B2 in RAW DATA sheet, it will be the only data referenced to cell in Campaign Report sheet.
If the pointer is in B2 on CReport Sheet require data not only from B2 in RAW DATA but also D2 Cell.
What script i need to add in my function ?
i'm expecting the chunk of code will something like this
function dataParse(input,dataSegment){
return Array.isArray(input) ? input.map(function(e){
return e.map(function(f){
if(f!=""){
segmentData=getSegment(f,dataSegment);
if(segmentData=="google"){
returnData=get reference from column D //<---
}else{
returnData=get reference from column E //<---
}
return returnData
}
}
)}
) : "false usage";
}
Hope its clear enough.
Thanks in Advance !
I modified your function in this way:
// range (String): It will be used to get the info in a range
function dataParse(input,dataSegment, range){
var val = "";
return Array.isArray(input) ? input.map(function(e, index){
return e.map(function(f){
if(f!=""){
// If col D has value google then take info from col B
if(f === "google") val = getDesiredRangeValue("B", range, index);
// else take info from col E
else val = getDesiredRangeValue("E", range, index);
// Take segment as needed
return getSegment(val,dataSegment);
}
}
)}
) : "false usage";
}
In order to make it work, I inserted an extra argument to the function. Now you will need to pass as an string the range in A1 notation in your ArrayFormula, this is because the input argument only gives you the values in the cells, and with that extra argument it will be possible to obtain extra info. To make it work fine, always use the same range as the next example shows:
=ArrayFormula(dataParse('RAW DATA'!D2:D5, 2,"D2:D5"))
or
=ArrayFormula(dataParse('RAW DATA'!D2:D, 2,"D2:D"))
Notice I also added a new function called getDesiredRangeValue, which will take the values from the column you need, depending if one of the cells from Col D has the value google. This is how the function looks:
/*
// A1 (String): The col from where you will want the info
// range (String): It will be used to get the info in a range
// index (Integer): It gives the index number from the main array gotten in the input arg
*/
function getDesiredRangeValue(A1, range, index){
var rowNumbers = range.match(/\d+/g);
// It checks if the range will has and end or it will prolong without specifying and end row
if(rowNumbers.length > 1){
var rangeCol = ss.getRange(A1 + rowNumbers[0] + ":" + A1 + rowNumbers[1]).getValues();
} else {
var rangeCol = ss.getRange(A1 + rowNumbers[0] + ":" + A1).getValues();
}
// It returns the whole value from each cell in the specified col
return rangeCol[index][0];
}
Code
Now your whole code will look like this:
// Global var
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("RAW DATA");
function getSegment(data,index){
temp=data.split("_");
return temp[index-1];
}
/*
// A1 (String): The col from where you will want the info
// range (String): It will be used to get the info in a range
// index (Integer): It gives the index number from the main array gotten in the input arg
*/
function getDesiredRangeValue(A1, range, index){
var rowNumbers = range.match(/\d+/g);
// It checks if the range will has and end or it will prolong without specifying and end row
if(rowNumbers.length > 1){
var rangeCol = ss.getRange(A1 + rowNumbers[0] + ":" + A1 + rowNumbers[1]).getValues();
} else {
var rangeCol = ss.getRange(A1 + rowNumbers[0] + ":" + A1).getValues();
}
// It returns the whole value from each cell in the specified col
return rangeCol[index][0];
}
// range (String): It will be used to get the info in a range
function dataParse(input,dataSegment, range){
var val = "";
return Array.isArray(input) ? input.map(function(e, index){
return e.map(function(f){
if(f!=""){
// If col D has value google then take info from col B
if(f === "google") val = getDesiredRangeValue("B", range, index);
// else take info from col E
else val = getDesiredRangeValue("E", range, index);
// Take segment as needed
return getSegment(val,dataSegment);
}
}
)}
) : "false usage";
}
Docs
These are the docs I used to help you:
Class Sheet
Custom Functions
I have been searching for a while and trying to work together a script from various answered topics that will allow me to adjust an adjacent cells content based on the data entered. I cannot seem to get it to work properly and need some help steering the ship the right direction. Here is what I am trying to accomplish:
--If the value of cell A2:A is a six digit number AND the value of cell D2:D (same row) is "MATCH" then the value for cell B2:B should be set to "ANN"
--If the value of cell A2:A is a six digit number AND the value of cell D2:D (same row) is "NO MATCH" then the value for cell B2:B should be set to "ANN" and a drop-down data validation list of ['ANN','RNW'] populate WITH the default value of the list set to "ANN"
--If the value of cell A2:A has a length of seven or greater characters then a drop-down data validation list of ['1DY','RNW','NEW'] populate WITH the default value of the list set to "1DY"
Is it even possible to set the value of a data validation cell to a specific, default value? This is important as when the user is entering data they will more than likely accept the default value. If they don't want the default value then they can select a value from the drop-down list.
I built a test sheet which shows the what the sheet should look like when data is filled out in column A and the associated values in column B.
My test is here: https://docs.google.com/spreadsheets/d/1p8sq63S-vSU1FKFLjtr2ZypItN5viXotoZL0Ki2PoQM/edit?usp=sharing
Here is the cobbled together script I was attempting to build (I too find it funny). This is my first attempt to right a Google Script to run on a spreadsheet.
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var aSheet = ss.getActiveSheet();
var aCell = aSheet.getActiveCell();
var aColumn = aCell.getColumn();
var aRow = aCell.getRow();
//var licenseStatus = aSheet.getRange(aRow, aColumn+9).getValue();
// The row and column here are relative to the range
// getCell(1,1) in this code returns the cell at B2, B2
var licenseTypeCell = aSheet.getRange(aRow, aColumn+1);
if (aColumn == 1 && aSheet.getName() == 'Onsite') {
if (isnumber(aCell) && (len(aCell) <= 6)) {
var rule = SpreadsheetApp.newDataValidation().requireValueInList(['ANN','RNW']).build();
licenseTypeCell.setValue("ANN");
licenseTypeCell.setDataValidation(rule);
} else {
var rule = SpreadsheetApp.newDataValidation().requireValueInList(['1DY','RNW','NEW']).build();
licenseTypeCell.setValue("1DY");
licenseTypeCell.setDataValidation(rule);
}
}
}
Any help/guidance would be greatly appreciated.
You are on the right track, few minor changes. Below you will find some new function to be used in your code.
1) getValue() You get your cell using var aCell = aSheet.getActiveCell() i.e the cell that was edited. But to get the value of the cell you will need to do the following aValue = aCell.getValue()
2) isNaN() To check if the aValue (as determined above) is a number or not. You will use a function called isNaN(aValue). Google script uses javascript platform and hence we need to use functions from javascript. This is different from an inbuilt function you use in a google spreadsheet. It returns True if the value is Not A Number(NAN). Hence, we use a not operator(!) to flip the return value, like so
if(!isNaN(aValue))
3) Number of digits There is no len function in google scripts, hence to determine if the number is 6 digits long you can do the following
if(aValue < 1000000)
Your final code will look something like this:
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var aSheet = ss.getActiveSheet();
var aCell = aSheet.getActiveCell();
var aColumn = aCell.getColumn();
var aRow = aCell.getRow();
//var licenseStatus = aSheet.getRange(aRow, aColumn+9).getValue();
// The row and column here are relative to the range
// getCell(1,1) in this code returns the cell at B2, B2
var licenseTypeCell = aSheet.getRange(aRow, aColumn+1);
var aValue = aCell.getValue()
if (aColumn == 1 && aSheet.getName() == 'Main') {
if (!isNaN(aValue) && aValue < 1000000) {
var matchCell = aSheet.getRange(aRow, aColumn+3).getValue()
//The above gets value of column D (MATCH or NO MATCH)
if(matchCell == "MATCH"){ //Check if Col D is MATCH
licenseTypeCell.setValue("ANN");
}
else{
var rule = SpreadsheetApp.newDataValidation().requireValueInList(['ANN','RNW']).build();
licenseTypeCell.setValue("ANN");
licenseTypeCell.setDataValidation(rule);
}
} else {
var rule = SpreadsheetApp.newDataValidation().requireValueInList(['1DY','RNW','NEW']).build();
licenseTypeCell.setValue("1DY");
licenseTypeCell.setDataValidation(rule);
}
}
}
Also, note the addition of the following lines to check for col D Value
var matchCell = aSheet.getRange(aRow, aColumn+3).getValue()
//The above gets value of column D (MATCH or NO MATCH)
if(matchCell == "MATCH"){ //Check if Col D is MATCH
licenseTypeCell.setValue("ANN");
}
I want to create list of all occurences of "x" string in range. This is my sheet:
And I want to search all occurences and list them and give proper names:
For example for G2, I want "Beret Grey" string as result. I think that I need to use array formula or something like that.
Let me first preface this that vba would be much more robust, but this formula will get you there. It may be slow as it is an array type formula and is doing a lot of calculations. These calculations only expound exponentially as the number of cells with them in it increases:
=IFERROR(INDEX(A:A,AGGREGATE(15,6,ROW($B$2:$G$7)/($B$2:$G$7="x"),ROW(1:1))) & " " & INDEX($1:$1,AGGREGATE(15,6,COLUMN(INDEX(A:G,AGGREGATE(15,6,ROW($B$2:$G$7)/($B$2:$G$7="x"),ROW(1:1)),0))/(INDEX(A:G,AGGREGATE(15,6,ROW($B$2:$G$7)/($B$2:$G$7="x"),ROW(1:1)),0)="x"),ROW(1:1)-COUNTIF($B$1:INDEX(G:G,AGGREGATE(15,6,ROW($B$2:$G$7)/($B$2:$G$7="x"),ROW(1:1)) -1),"x"))),"")
You will need to expand the range to what you need. Change all the $B$2:$G$7 to $B$2:$N$29. Do not use full column references outside those that I have used. It will kill Excel.
Also note what is and what is not relative references, they need to remain the same or you will get errors as the formula is dragged/copied down.
As simple UDF to do what you want:
Function findMatch(rng As Range, crit As String, inst As Long) As String
Dim rngArr() As Variant
rngArr = rng.Value
Dim i&, j&, k&
k = 0
If k > Application.WorksheetFunction.CountIf(rng, crit) Then
findMatch = ""
Exit Function
End If
For i = LBound(rngArr, 1) + 1 To UBound(rngArr, 1)
For j = LBound(rngArr, 2) + 1 To UBound(rngArr, 2)
If rngArr(i, j) = crit Then
k = k + 1
If k = inst Then
findMatch = rngArr(i, 1) & " " & rngArr(1, j)
Exit Function
End If
End If
Next j
Next i
then you would call it like this:
=findMatch($A$1:$G$7,"x",ROW(1:1))
And drag/copy down.
I have a worksheet were I am trying to concatenate dynamic text values based on =TODAY()
So I have B3:B1000 being the fields where users will enter in text. D3:D1000 is where the user enters the date they filled it in. I3 is =TODAY()
How do I concatenate text values in B3:B1000 based on if the dates in the D3:D1000 = I3? and have that concatenation always update based on I3?
I would also need a delimiter of ", "
Got it working after some trail and error and some deeper searching :)
Function ConcatenateIf(CriteriaRange As Range, Condition As Variant, _
ConcatenateRange As Range, Optional Separator As String = ",") As Variant
Dim i As Long
Dim strResult As String
On Error GoTo ErrHandler
If CriteriaRange.Count <> ConcatenateRange.Count Then
ConcatenateIf = CVErr(xlErrRef)
Exit Function
End If
For i = 1 To CriteriaRange.Count
If CriteriaRange.Cells(i).Value = Condition Then
strResult = strResult & Separator & ConcatenateRange.Cells(i).Value
End If
Next i
If strResult <> "" Then
strResult = Mid(strResult, Len(Separator) + 1)
End If
ConcatenateIf = strResult
Exit Function
ErrHandler:
ConcatenateIf = CVErr(xlErrValue)
End Function
and then used this concatenate function =ConcatenateIf(D3:D1000,I3,B3:B1000,", ")