Convert numbers to non-numbers in Excel - excel

I notice that numeric values like 123456 can be considered as numbers or non-numbers in Excel. Mixing numbers and non-numbers may result in unexpected results of = or XLOOKUP.
For instance, in the following worksheet, the formula of D3 is =ISNUMBER(C3) and the formula of D4 is =ISNUMBER(C4). Their values are not the same. Then =C3=C4 in another cell will return FALSE; =XLOOKUP(C3,C4,C4) will return #N/A.
So one solution to avoid such surprises is that I would like to convert all these numeric values from numbers to non-numbers, before applying formulas on them.
Does anyone know if it is possible to undertake this conversion by manual operations (select the range, then...)?
Does anyone know how to achieve this conversion by a subroutine in VBA (select the range, then run the VBA subroutine, then the selected range will be converted)?

If you firstly write numbers in a range, let us say "C:C", formatted as General, any such a cell will return TRUE when you try =ISNUMBER(C4).
If you preliminary format the range as Text and after that write a number, this will be seen by Excel as a String (non-numbers, as you say...) and =ISNUMBER(C4) will return False.
Now, if you will try formatting the range as Text after writing the numbers these cells will not be changed in a way to make =ISNUMBER(C4) returning FALSE. In order to do that, you can use TextToColumns, as in the next example:
Private Sub testTextToCol()
Dim sh As Worksheet, rng As Range
Set sh = ActiveSheet
Set rng = sh.Range("C:C")
rng.TextToColumns Destination:=rng, FieldInfo:=Array(1, 2)
End Sub
It will make the existing =ISNUMBER(C4), initially returning TRUE, to return FALSE...

Of course you cannot compare apples to oranges, thus strings are not comparable to integers/longs/numbers. Make sure that all you compare are apples.
In a routine this would be s.th. like
Option Explicit
Sub changeFormat():
' Declare variables
Dim Number As Variant
Dim check As Boolean
'Converts the format of cells D3 and D4 to "Text"
Range("D3:D4").NumberFormat = "#"
'Assign cell to be evaluated
Number = Range("D3")
Debug.Print Number 'Prints '123'
check = WorksheetFunction.IsText(Trim(Sheets("Tabelle1").Cells(4, 3)))
Debug.Print check 'Prints True
'Converts the format of cells D3 and D4 to "Numbers"
Range("D3:D4").NumberFormat = "0.00"
'Compare Cells
If Range("D3").NumberFormat = Range("D4").NumberFormat Then Range("D5").Value = "Same Format"
End Sub
Also see the docs

Related

How to alternate row color in a table when cell value changes?

I have a spreadsheet that brings in a table of data via Power Query.
Every time there is a refresh, the table length may change.
I need columns C and D to alternate highlight colors when the value is not the same as the previous row.
How the table should look each time it is refreshed through Power Query and the VBA code runs.
(1) Attempt with conditional formatting:
(Note: This will work correctly only if a value cannot appear later down that list).
Create a rule (or two rules, to be precise) based on a formula. According to your screenshot, I assume that your data starts at row 3 and you want to look at column C.
There is a rather easy formula that you can use to count the number of unique values of a list. The base formula was "borrowed" from ExcelJet and is =SUMPRODUCT(1/COUNTIF(data,data)). The trick now is that you look to the range from the beginning of the list to the actual row by using the range C$3:C3. If you copy the formula down, the start row remains while the end row is changed. Now simple put a IsOdd resp IsEven around the formula.
So the formula is =ISODD(SUMPRODUCT(1/COUNTIF(C$3:C3,C$3:C3))) for the "green" rows and =ISEVEN(SUMPRODUCT(1/COUNTIF(C$3:C3,C$3:C3))) for the yellow. Apply the rule to the range =$C$3:$C$1048576
However, I don't know if this conditional formatting will "survive" an update of the query.
(2)
Formatting with VBA is simple. The following code formats one column of an table (a table in VBA is the type ListObject. Pass the listobject and the column number as parameter:
Sub ColorRows(table As ListObject, columnNumber As Long)
Dim cell As Range, isOdd As Boolean
For Each cell In table.DataBodyRange.Columns(columnNumber).Cells
With cell
If .Offset(-1, 0) <> .Value Then isOdd = Not isOdd
' On a standard Office color scheme, 10 is kind of green and 8 is a dirty yellow
.Interior.ThemeColor = IIf(isOdd, 10, 8)
.Interior.TintAndShade = 0.8
End With
Next
End Sub
This is how the call could look like (adapt the sheet and the listObject to your needs):
Sub test()
ColorRows ThisWorkbook.Sheets(1).ListObjects(1), 3
End Sub
Now calling this code automatically is a different story and unfortunately rather complicated - if you want/need, try https://stackoverflow.com/search?q=vba+QueryTable+After+Refresh for some insights. An easy alternative is to trigger the formatting manually, eg by placing a button (or a shape) on your sheet that calls the code.
The VBA to apply the format condition for the alternating coloring would be:
Public Sub alternatingColorSizeAndKind(rg As Range)
Dim fc As FormatCondition
With rg.FormatConditions
.Delete
Set fc = .Add(xlExpression, , "=($C1=$C2)+($C2=$C3)")
fc.Interior.Color = 14348258
Set fc = .Add(xlExpression, , "=($C1<>$C2)")
fc.Interior.Color = 13431551
End With
End Sub
You have to pass the range of your table to this sub.
If you have a listobject/table then you call it like this:
Public Sub update()
Dim lo As ListObject
'>>> adjust the names to your needs
Set lo = ThisWorkbook.Worksheets("Sheet1").ListObjects("Pull_Data")
alternatingColorSizeAndKind lo.DataBodyRange
End Sub

Convert vlookup to more than 255 characters via Excel VBA

I am looking for reverse vlookup with more than 255 characters in Excel VBA.
This is the formula based one which I took from this website.
=INDEX(F2:F10,MATCH(TRUE,INDEX(D2:D10=A2,0),0))
I have try to convert it in VBA. Here below sample code
Sub test()
'concat
Range("i1") = WorksheetFunction.TextJoin(" ", True, Range("g1:h1"))
'lookup
Sal1 = Application.WorksheetFunction.Index(Sheets("sheet1").Range("a1:a2"), Application.WorksheetFunction.Match(True, Application.WorksheetFunction.Index(Sheets("sheet1").Range("i1:i1") = Range("i1").Value, 0), 0))
'=INDEX($W$3:$W$162,MATCH(TRUE,INDEX($W$3:$W$162=U3,0),0))
End Sub
It works well but it didn't when i change the range("i1:i1") to range("i1:i2")
I'm not sure what that worksheet formula does that =INDEX(F2:F11,MATCH(A2,D2:D11,FALSE)) doesn't do.
This part Index(Sheets("sheet1").Range("i1:i2") = Range("i1").Value, 0) is comparing a 2-d array to a single value, which should result in a Type Mismatch error. Whenever you reference a multi-cell range's Value property (Value is the default property in this context), you get a 2-d array even if the range is a single column or row.
You could fix that problem with Application.WorksheetFunction.Transpose(Range("D1:D10")) to turn it into a 1-d array, but I still don't think you can compare a 1-d array to a single value and have it return something that's suitable for passing into INDEX.
You could use VBA to create the array's of Trues and Falses, but if you're going to go to that trouble, you should just use VBA to do the whole thing and ditch the WorksheetFunction approach.
I couldn't get it to work when comparing a single cell to a single cell like you said it did.
Here's one way to reproduce the formula
Public Sub test()
Dim rFound As Range
'find A2 in D
Set rFound = Sheet1.Range("D1:D10").Find(Sheet1.Range("A2").Value, , xlValues, xlWhole)
If Not rFound Is Nothing Then
MsgBox rFound.Offset(0, 2).Value 'read column f - same position as d
End If
End Sub
If that simpler formula works and you want to use WorksheetFunction, it would look like this
Public Sub test2()
Dim wf As WorksheetFunction
Set wf = Application.WorksheetFunction
MsgBox wf.Index(Sheet1.Range("F2:F11"), wf.Match(Sheet1.Range("A2").Value, Sheet1.Range("D2:D11"), False))
End Sub
Function betterSearch(searchCell, A As Range, B As Range)
For Each cell In A
If cell.Value = searchCell Then
betterSearch = B.Cells(cell.Row, 1)
Exit For
End If
betterSearch = "Not found"
Next
End Function
i found this code from above link and it is useful for my current search.Below examples i try to get value..
Kindly consider Row 1 to 5 as empty for A and B column because my table always start from Row 6
Row
A Column
B Column
6
54
a
7
55
b
8
56
c
VBA Code:
Sub look_up ()
Ref = "b"
look_up = betterSearch(Ref, Range("B6:B8"), Range("A6:A8"))
End Sub
it show Empty while use Range("B6:B8"), Range("A6:A8")
but when changing the range from B6 and A6 to B1 and A1 (Range("B1:B8"), Range("A1:A8") )it gives the value...
My question is "can get the values from desired range"
Expressing matches via VBA
I like to know if there (are) any possibilities to convert this formula.
=INDEX(F2:F10,MATCH(TRUE,INDEX(D2:D10=A2,0),0))
So "reverse VLookUp" in title simply meant to express the (single) formula result via VBA (btw I sticked to the cell references in OP, as you mention different range addresses in comments).
This can be done by simple evaluation to give you a starting idea:
'0) define formula string
Dim BaseFormula As String
BaseFormula = "=INDEX($F$2:$F$10,MATCH(TRUE,INDEX($D$2:$D$10=$A2,0),0))"
'1) display single result in VB Editor's immediate
Dim result
result = Evaluate(BaseFormula)
Debug.Print IIf(IsError(result), "Not found!", result)
On the other hand it seems that you have the intention to extend the search string range
from A2 to more inputs (e.g. till cell A4). The base formula wouldn't return a results array with this formula,
but you could procede as follows by copying the start formula over e.g. 3 rows (note the relative address ...=$A2 to allow a row incremention in the next rows):
'0) define formula string
Dim BaseFormula As String
BaseFormula = "=INDEX($F$2:$F$10,MATCH(TRUE,INDEX($D$2:$D$10=$A1,0),0))"
'2) write result(s) to any (starting) target cell
'a)Enter formulae extending search cells over e.g. 3 rows (i.e. from $A2 to $A4)
Sheet3.Range("H2").Resize(3).Formula2 = BaseFormula
'b) optional overwriting all formulae, if you prefer values instead
'Sheet3.Range("H2").Resize(3).Value = Tabelle3.Range("G14").Resize(3).Value
Of course you can modify the formula string by any dynamic replacements (e.g. via property .Address(True,True,External:=True) applied to some predefined ranges to obtain absolute fully qualified references in this example).
Some explanations to the used formulae
The formula in the cited link
=INDEX(F2:F10,MATCH(TRUE,INDEX(D2:D10=A2,0),0))
describes a way to avoid an inevitable #NA error when matching strings with more than 255 characters directly.
Basically it is "looking up A2 in D2:D10 and returning a result from F2:F10" similar to the (failing) direct approach in such cases:
=INDEX(F2:F11,MATCH(A2,D2:D11,FALSE))
The trick is to offer a set of True|False elements (INDEX(D2:D10=A2,0))
which can be matched eventually without problems for an occurence of True.
Full power by Excel/MS 365
If, however you dispose of Excel/MS 365 you might even use the following much simpler function instead
and profit from the dynamic display of results in a so called spill range.
That means that matches can be based not only on one search string, but on several ones (e.g. A1:A2),
what seems to solve your additional issue (c.f. last sentence in OP) to extend the the search range as well.
=XLOOKUP(A1:A2,D2:D10,F2:F10,"Not found")

How do I update formula range in a macro?

I have a formula in A1 that is fed with data from a different workbook. It's only a reference to this other workbook, there aren't any calculations.
This second workbook is updated on a monthly basis so the cell I'm interested in referring to is offset one cell to the right each month.
How can I write a macro that tells my current formula in A1 to use the same formula but moving it one place to the right? It'd be something like: [Book1]Sheet1!C15 to [Book1]Sheet1!D15. Thanks!
Use Range.Precedents to get the cells a particular Range depends on.
'get the cell:
Dim theCell As Range
Set theCell = ActiveSheet.Range("A1")
'get its first "precedent" Range:
Dim precedent As Range
Set precedent = theCell.Precedents(1)
'rewrite the formula, offsetting the precedent by 1 column:
theCell.Formula = "=" & precedent.Offset(ColumnOffset:=1).Address(External:=True)
Obviously this makes a lot of assumptions and will need to be adjusted to your specific needs, but you don't need to parse any formulas to offset its precedent cells when you're looking at a formula that's simply =SomeCellAddress.
First put this small UDF in a standard module:
Public Function NextCellOver(s As String) As String
arr = Split(s, "!")
addy = Range(arr(1)).Offset(0, 1).Address
NextCellOver = arr(0) & "!" & addy
End Function
It will accept a string that ends with something like !Z99 and return a string ending with !AA99. (Basically one column to the right.)
Then enter:
Sub marine()
With Range("A1")
.Formula = NextCellOver(.Formula)
End With
Application.CalculateFullRebuild
End Sub
To apply this to the cell in question.

How can I convert between 2 currencies USD and AED #rate of 3.68 throughout the rest of my workbook but only in selected cell ranges on each sheet?

For example. I want my intro sheet "Main Sheet" to have an option to switch the workbook between currencies. USD and AED at the rate of 3.68. Some cells are referencing other cells in different sheets, so I don't want to change the cell references, I only need to calculate the rate in specific cells within each sheet.
How can I accomplish this preferably using a check box or button for easy converting from the start. I'm using excel for Mac. Thank you
Create a cell with a validation drop-down allowing to choose between AED and USD. Convert that cell to a named range for easy referencing throughout the workbook. You might call it "Curr", short for "Currency" (short because it will be used often).
I recommend that you create a similar cell somewhere where you enter the rate, currently 3.68 but plan on changing the rate in that cell only and have it applied to all the workbook. Name that cell as "Rate".
Now all cells containing values which you may want switched would be subject to the following formula. =[CellValue] * IF(Curr = "AED", Rate, 1). This formula presumes that the values are all entered in USD. If they are entered in AED the formula should look as follows. = ROUND([CellValue] / IF(Curr = "AED", 1, Rate), 2)
As you see, this solution would require the original cell values to be recorded somewhere, meaning, the cells used for data capture can't be the same as the ones used for data display. If you wish to insist on capture and display being in the same cell you would need code to do the conversion.
On the face of it this seems simple: When the Curr selection is changed, all cells with affected values are re-calculated. In practise this would end in disaster because there are 1001 ways in which something might go wrong and then you would lose all your data, not knowing whether the values are USD or AED at that moment.
Therefore the starting point needs to be to separate data capture and data display. Once that is done workheet functions might well be not only the easiest but also the most efficient way of achieving what you want.
I'm going to assume that you want to have the conversion on the input cell and not all of your cells are formulas and that a lot of the cells you want to convert are values. You should seriously consider the answer to split out input vs display, it will be much more foolproof and protected from any logic that may break your workbook.
If you're keen on this pathe then do the following, but, before you do ... BACKUP YOUR WORKBOOK. Any tests I've done with the below code are not breaking but I don't have your workbook, therefore, I make no guarantees.
Firstly, you need a cell that gives you the current exchange rate. You need to give that cell a named range of ExchangeRate.
In my workbook, that cell contains a formula ...
=IF(B1="USD",1,3.68)
It looks like this ...
... and cell B1 has a validation attached to it that allows you to select from 2 currencies, AED or USD.
You said you want to be able to ensure that only a selection of cells will be converted. To make sure we ring fence just those cells, you need to create a named range ON EACH SHEET that includes all of those cells.
The name of that range needs to be called CellsToConvert and you can do that through the Name Manager. When creating the named range, make sure you specify the worksheet you're creating it for, do not selected the "Workbook" option.
... the below shows the sporadic range I used on the first sheet. All coloured cells a part of that range. The green cells contain values and the yellow cells contain formulas.
At the end of the day, that range can be huge and across different sheets but it should work.
Now, add the following code into the ThisWorkbook object within the VBA editor ...
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim objCell As Range, dblExRate As Double, strFormula As String, objSheet As Worksheet
Dim strNewFormula As String, strOpeningChar As String, bIsFormula As Boolean
Dim objCells As Range, strError As String, strExRateRangeName As String
strExRateRangeName = "ExchangeRate"
dblExRate = Range(strExRateRangeName)
Application.EnableEvents = False
For Each objSheet In Worksheets
On Error Resume Next
strError = ""
Err.Clear
Set objCells = objSheet.Range("CellsToConvert")
strError = Err.Description
On Error GoTo 0
If strError = "" Then
For Each objCell In objCells
strFormula = objCell.FormulaR1C1
bIsFormula = False
' Check to make sure this field contains a formula.
If Left(strFormula, 1) = "=" And objCell.NumberFormat <> "#" Then
bIsFormula = True
End If
If dblExRate = 1 Then
' Base currency selected.
' Check to see if the cell contains a formula, if it does,
' convert it back to a value
If bIsFormula Then
' It's a formula and the cell is not set to text, proces it back
' to its original value, that could still be a formula.
' Remove all of the exchange rate components we would've added as
' a part of this routine.
strNewFormula = Replace(strFormula, ") * " & strExRateRangeName, "")
' Check to see if the formula has changed against the previous statement,
' if it has, then it contained the custom additions, otherwise, it didn't.
If strFormula <> strNewFormula Then
strNewFormula = Mid(strNewFormula, 3)
' Check to see if the new value is numeric, if it is, remove the leading
' equals sign as it wasn't originally a formula, or, at least it doesn't
' need to be a formula.
If IsNumeric(strNewFormula) Then
objCell.Value = strNewFormula
Else
objCell.FormulaR1C1 = "=" & strNewFormula
End If
End If
End If
Else
' Something other than the base currency has been selected.
strNewFormula = objCell.FormulaR1C1
If InStr(1, strNewFormula, strExRateRangeName, vbTextCompare) = 0 Then
If bIsFormula Then strNewFormula = Mid(objCell.FormulaR1C1, 2)
objCell.FormulaR1C1 = "=(" & strNewFormula & ") * " & strExRateRangeName
End If
End If
Next
End If
Next
Application.EnableEvents = True
End Sub
... once you've done all of the above, it should work for you. Performance could be tested if the workbook is large but that's something you'll need to check for yourself.
If you change a cell and it's within one of those ranges AND the currency of USD is not selected, you'll see the input value changed to a formula after you hit enter. That's pretty neat when you think about it but may not be for you.
One last thing to note, if your range contains broken links, the calculation for that sheet will fail and my code will not notify you of that.
This adds another option for you but is riskier than the first answer. There's nothing like options. :-)

VBA - Excel cells populated with 1d array aren't blank

I fill cells with 1d array defined as Variant. All 3 variables in this array are public string variables. Sometimes variable is VbNullString and goes as such into an array.
Unfortunately while selecting empty cells in Excel which are corresponding to VbNullString values, they are count as non blank and =IsBlank() function returns false. A check from VBA's immediate window doesn't detect anything in example cell:
?"check" & range("f4").Value & "character"
Result is: "checkcharacter"
How can I clear these cells to really be blank and change something in my macro not to populate them?
Dim results As Variant
results = Array(Company, Address, Phone)
With WS
Range(.Cells(resultCounter, 1), .Cells(resultCounter, UBound(results) + 1)).Value2 = results
End With
Cannot reproduce the issue from your description of what you're doing:
Dim Company As String, Address As String, Phone As String
Sub Tester()
Dim results As Variant
Company = "CpmpanyA"
Address = vbNullString
Phone = "555-1212"
results = Array(Company, Address, Phone)
With ActiveSheet
.Range(.Cells(20, 1), .Cells(20, UBound(results) + 1)).Value2 = results
End With
Debug.Print ActiveSheet.Evaluate("ISBLANK(B20)") '>> ###True###
End Sub
I have found the cause. The error happens when cell format is set to Text before running the macro. When I change it to General, cells which should be blank are blank. Changing cell format after macro has been executed has no effect on this behavior.
Also I have tested that assigning cell value of VbNullString works fine with Text format cells. It must be the array, which is Variant, causing this behavior.
It's a pity, because I hoped to have both a proper counter of values when I select column and assurance that values won't be transformed into something they should not be by General format setting.

Resources