Any suggestions for Excel vba code that will identify cells that contain values but no function. The catch being that the values I'm searching for are preceded by an "=" sign.
I often have to review other people's financial spreadsheets and knowing which cells contain the actual inputs (assumptions) is key. I can easily find cells with just a constant input with a simple:
Selection.SpecialCells(xlCellTypeConstants, 1).Select
The problem is some users have a habit of inputing numbers preceded by an = sign and these cells are treated by excel as containing a function. I know I can search for cells with functions too, but I only want to find the ones that don't actually have a function, but are input starting with an = sign.
In other words, instead of inputing 1000 in a cell the user inputs =1000. It is only these types of cells I am trying to highlight.
Any suggestions on how I can identify these cells?
Exactly this "The problem is some users have a habit of inputing numbers preceded by an = sign" is your problem. Stop people having this habit!
First it is a stupid habit because it slows everything down because Excel has to evaluate those cells that contain something like =1000 to find out the value and
second those cells actually contain a formula and not a value. So there is no built in way to distinguish them.
Those people are just using Excel wrong.
There is only one workaround I can imagine and that is looping through all cells that contain a formula, remove the = sign and check if the rest of the "formula" is numeric IsNumeric(). If so replace the formula by its value.
So for example if the cell is =1000 then 1000 is numeric and it will replace it by the value 1000. If the cell is =1000*2 then 1000*2 is not numeric because it contains a * sign and it will keep the formula.
Note that this workaround is just fighting symptoms but not fixing the root cause. The root cause is those people using Excel wrong! And you should always fix the causes not fight the symptoms.
Option Explicit
Public Sub ReplaceFormluasWithValueIfNoCalculation()
Dim AllCellsWithFormulas As Range
On Error Resume Next ' next line errors if no formulas are found, so hide errors
Set AllCellsWithFormulas = ActiveSheet.Cells.SpecialCells(xlCellTypeFormulas)
On Error GoTo 0 ' re-enable error reporting
If Not AllCellsWithFormulas Is Nothing Then
Dim Cell As Variant
For Each Cell In AllCellsWithFormulas.Cells ' loop through all formulas
Dim ExtractedValue As String ' extract formula right of the `=` sign
ExtractedValue = Right$(Cell.Formula, Len(Cell.Formula) - 1)
' check if extracted formula is only a value, if so replace formula with value
If IsNumeric(ExtractedValue) Then
Cell.Value = ExtractedValue
End If
Next Cell
End If
End Sub
Even the above can be done, I recommend fixing the root cause.
Assuming those inputs only contain number you could use a regex for that and ensure the value only contain "=" and numbers you'll have a code like this :
Dim RegEx As Object, MyString As String
Set RegEx = CreateObject("VBScript.RegExp")
MyString = cell.formulas
With RegEx
.Pattern = "=*[0-9., ]+"
End With
RegEx.test(MyString) 'True if MyString is a number false if it's a function
You'll need to activate "Microsoft VBScript Regular Expression" though.
Related
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")
I want to automatically find cells that contain formulas only being a simple reference. For example:
=D20 or ='Sheet1'!D20.
The purpose is that I want to automatically color code these cells.
Is there a way in VBA to search for formula types?
After some time to let this sink in, I think it's not that straightforward. Direct cell references can include simple references like:
=A1
But there are instances where you can refer to another cell in another sheet, another opened workbook and even another closed workbook. While this makes it difficult to use RIGHT, LEFT functions or even FORMULATEXT for the likes, the one thing that you can do is to check for patterns.
Any direct cell reference cannot include any other calculation, meaning it must exclude any unwanted operater (/,*,-,+). As far as I'm concerned this means that if we check a cell that only has ONE Precedent, the formula can NOT start with a digit NOR can it end with digits after any of the above mentioned operators.
Our best bet here, is to put these criteria in a regular expression. I'm by no mean a RegEx expert, but I think the following does the trick:
"^=.*[A-Z]+\$*\d+$"
Start of the line is =
Followed by any word/non-word character, 0-n times
End of the line must be digits, 1-n times, preceded by either just upper case alfabetic characters or a combination with the dollar sign (for absolute cell references)
The above works as long as we include a check for just one .Precedent and a check if the second character of the formula isn't numeric. The code example would then, for example, look like:
Sub FindFormualaType()
Dim rng1 As Range, rng2 As Range, cl As Range
Dim regex As New RegExp
Dim matches As Object
Dim tststr As String
With regex
.Global = True
.Pattern = "^=.*[A-Z]+\$*\d+$"
End With
With Sheet1 'Change to your sheet's codename
Set rng1 = Intersect(.Cells, .Cells.SpecialCells(-4123))
For Each cl In rng1
On Error Resume Next
If cl.Precedents.Count = 1 Then
If Left(cl.Formula, 2) Like "=#" = False Then
Set matches = regex.Execute(cl.Formula)
If matches.Count = 1 Then
If Not rng2 Is Nothing Then
Set rng2 = Union(rng2, cl)
Else
Set rng2 = cl
End If
End If
End If
End If
On Error GoTo 0
Next cl
End With
rng2.Interior.Color = vbGreen
End Sub
To explain some of the steps in the code above:
To use the RegExp you have to set a reference to it:
The code then makes use of .SpecialCells with xlCellTypeFormulas (-4123), and creates a .Range object through .Intersect. Therefore we don't have to loop all cells to check with .HasFormula (hopefully saving some time).
Next up is the loop through all cells in that newly created .Range object and check if Precendents.Count = 1. We need to capsule that into an On Error Resume Next because it would error out if no precedents are found, e.g.: a formula like ="Hello".
When the count equals 1, we check with the Like operator if we can savely execute the regex and test for a match on the used pattern.
Because I would rather do a fill operation in one go, I suggest to create another .Range object (rng2) and use Union to set this object.
Finally we can use something like rng2.Interior.Color = vbGreen to color all cells that just have a direct reference to just one cell.
Hope that helped. By no means I'm an RegEx expert and it can probably be done simpler.
Some background information on why I used the Like operator. Because I wanted to be sure no calculation is done with the cell reference, the first character in the equation can NOT be numeric. Within RegEx you got something called "lookahead". Though, unfortunately, I couldn't get the expression to work withing VBA (unsupported?) and therefor used the Like operator to make sure the second character was not numeric.
Create a loop to go through all cells with formulas (.HasFormula), get the formula text of the cell as a string, take all formulatext except the first character (RIGHT( text, (LEN(text) - 1))) and then test if that string is a valid Range. If it's not, then there is more in the cell then 'just a simple reference'. Good luck with figuring out the code :)
Use FORMULATEXT() function in Conditional Formatting to highlight automatically cells that contains your formula. See below formula for Conditional Formatting
=OR(FORMULATEXT(A1)="=Sheet1!D20",FORMULATEXT(A1)="=D20")
I want to use the Find and Replace in Excel to remove all zeroes from a column that holds say the values of column A-column C. So A2-C2, A3-C3 etc. But because its a formula it just wont work even though I have tried the 'match case/Match Entire contents' as well as other combinations in Options. This is a simple question but hard finding an answer as all questions seem to be directed at finding a part of a formula and replacing with something else.
If you do not want to see a "0" or "#VALUE!" in these columns, I would rewrite the formula such that it excludes these values as outputs (meaning any sum or avg function would exclude these cells). Try using either "iferror" or "if" formulas to exclude see examples below.
Excluding 0 using if statement: =IF(H8*G8 = 0,"",H8*G8)
Excluding #Value! using iferror: =IF(H8*G8 = 0,"",H8*G8)
Excluding 0 and #value using combined iferror/if statement: =IFERROR(IF(G20*H20 = 0," ",G20*H20),"")
If you want to exclude both 0s and #value! you can combine the formulas:
enter image description here
Assuming you do not want to rewrite the whole range of formulas, you dan do it via VBA (hit Alt+F11).Just paste that code somewhere in there. Now select the range you want to empty out of 0's and #Errors and run the code:
Sub RemZeroErr()
Dim rng As Range
For Each rng In Intersect(Selection, Selection.Parent.UsedRange).Cells
If IsError(rng) Then
rng.Value = ""
ElseIf rng.Value = 0 Then
rng.Value = ""
End If
Next
End Sub
First time posting for me and hoping to get some help with VBA for selective hardcoding.
I currently have a column into which a formula is set which returns either blank or a variety of text strings (the status of our company's orders).
I need to make a macro that looks into all the cells of that column and copy/pastes as value into that same cell only if the formula in that cell returns text string "Received". It should not affect the other cells where the formula is returning either blank or a different text string.
Would really appreciate your help. Please let me know if you need more info.
Thanks in advance,
Olivier
Put the following in the VBA project of your workbook:
Option Compare Text
Sub replaceThem()
Dim r As Range
Dim c
Set r = Range("B1:B3") ' use the actual range here
For Each c In r
If c.Value = "Received" Then c.Formula = "Received"
Next
End Sub
This will do what you asked. c.Value returns the value of the formula in the cell c, c.Formula replaces the formula. The Option Compare Text makes the comparison case-insensitive.
When iterating through cells in a worksheet, how can I get what the format setting on the cell is? Because based on this, I would like to build a SQL statement to either add the single ticks or not to the value retreived
Sounds like you need the VarType() function. Vartype(Range("A1"))
OK, so you don't want to know the format setting for the cell, but whether the value is numeric.
Can you just call IsNumeric(Range("A1")) and quote it if False?
Based on your comment that some numbers are stored as text in the DB, you are not going to solve this by a simple formula. Can't you just quote the values as you build your SQL statement?
Try using the following in VBA:
Range("A1").NumberFormat = "0.00" 'Sets cell formatting to numeric with 2 decimals.
Range("A1").Formula = "=Text(6, " & """0.00""" & ")" 'Looks like a number _
' but is really text.
Debug.Print WorksheetFunction.IsNumber(Range("A1")) 'Prints False
Range("A1").Value = 6 'Puts number into the cell, which also looks like 6.00
Debug.Print WorksheetFunction.IsNumber(Range("A1")) 'Prints True
This should tell you if the value is really text or really a number, regardless of the cell's formatting properties.
The key is that the intrinsic Excel IsNumber() function works better for this purpose than the VBA function IsNumeric. IsNumber() tells you whether the cell's value is a number, whereas IsNumeric only tells you if the cell is formatted for numeric values.
I don't think there's any property of a cell that indicates whether the cell actually contains a numeric value, although VarType() might help, it gets tricky because Excel will allow a number-formatted cell to contain string, and a text formatted cell to contain numeric values, without overriding the NumberFormat property.
In any case you likely need some independent test to figure out whether a cell IsNumeric (or other criteria) AND whether its NumberFormat is among an enumerated list which you can define.
Sub numFormat()
Dim cl As Range
Dim numFormat As String
Dim isNumber As Boolean
For Each cl In Range("A1")
numFormat = cl.NumberFormat
isNumber = IsNumeric(Trim(cl.Value))
Select Case numFormat
Case "General", "0", "0.0", "0.00" ' <--- modify as needed to account for other formats, etc.
If isNumber Then
Debug.Print cl.Address & " formatted as " & numFormat
End If
Case Else
'ignore all other cases
End Select
Next
End Sub
I don't think the format of the cell is the important thing. Rather, it's the data type of the field in your database. If you have the string 'foobar' in a cell and you create an INSERT INTO sql statement that attempts to put that into a Long Integer field, it's going to fail regardless of tickmarks.
Conversely, if a cell contains a numeric value (like 100) that needs to go into a VARCHAR field, it will need tickmarks (like '100').
If you're using ADO, check the Type property of the Field object to determine the data type. Use this list http://support.microsoft.com/kb/193947 to see what the types are. Then set up the SQL statement according to the field type.