Scenario
I am trying to use COUNTIF in my VBA code to count total how many similar string found in a range. But realised COUNTIF is not case sensitive. Eg: I want to count how many Apple in a range. But sometimes if in that range got apple, it is counting that also. Code as below. Consider Range(poRange) something like C1:C100 and Trim(mainpage.po.value) as Apple
iVal = Application.WorksheetFunction.CountIf(Range(poRange), Trim(mainPage.po.Value))
Thus I decided to use another method which is
iVal = Application.WorksheetFunction.SumProduct(--(EXACT(Range(poRange), Trim(mainPage.po.Value))))
But this time I couldn't run my VBA due to Compile time error 'Sub or Function not defined'
Could someone help me please?
The issue is with the EXACT function, which WorksheetFunction does not expose.
For unexposed Excel functions, a common workaround is to convert the expression to a string and call the Evaluate function. A rudimentary routine would look like this:
Public Function SumProductExact(rng As Range, testItem As String) As Long
Dim evalExpr As String
On Error GoTo EH
evalExpr = "=SUMPRODUCT(--(EXACT(" & rng.Address & ", """ & testItem & """)))"
SumProductExact = Evaluate(evalExpr)
Exit Function
EH:
SumProductExact = -1
End Function
and would be called like so:
Debug.Print SumProductExact(Sheet1.Range("A1:A10"), "Apple")
But it hardly seems worth going to those lengths for a relatively trivial function, so you could just write your own CountIf function for an exact match. You might want to expand this to cater for other type comparisons, like dates, but fundamentally it would look like this:
Public Function CountIfExact(rng As Range, testItem As Variant) As Long
Dim v As Variant
Dim c As Long
On Error GoTo EH
For Each v In rng.Value2
If v = testItem Then c = c + 1
Next
CountIfExact = c
Exit Function
EH:
CountIfExact = -1
End Function
which would be called as follows:
Debug.Print CountIfExact(Sheet1.Range("A1:A10"), "Apple")
I used one of the above solutions and it solved the problem. Entire code as below
Public c As Integer
Sub findVal()
Call SumProductExact(Sheet1.Range("A1:A10"), "Apple")
MsgBox c ' You can use this c value for other calculations
End Sub
Public Function SumProductExact(rng As Range, testItem As String) As Long
Dim evalExpr As String
On Error GoTo EH
evalExpr = "=SUMPRODUCT(--(EXACT(" & rng.Address & ", """ & testItem & """)))"
SumProductExact = Evaluate(evalExpr)
c = SumProductExact
Exit Function
EH:
SumProductExact = -1
End Function
Take note that for the above code, it is using static range which is A1:A10 and a static name Apple. You may change it to a dynamic range and name according to your needs.
Related
I have a problem:
Public Sub ChangeRow(Column As String, Value As String, id As Integer)
For i = 4 To 15
For Each rw In Worksheets(i).Rows
If Worksheets(i).Range("A" & rw.row).Value = id Then
Dim row As Integer
**row = getRow(id, i)**
MsgBox (row)
If Worksheets(i).Range(Column & rw.row).Value <> Value Then
Worksheets(i).Range(Column & rw.row) = Value
End If
Exit For
End If
Next rw
Next i
End Sub
Function getRow(id As Integer, Sheet As Integer) As Integer
For Each rw In Worksheets(Sheet).Rows
If Worksheets(Sheet).Range("A" & rw.row).Value = id Then
getRow = rw.row
End If
Next rw
End Function
Change Row Works fine... its just when I add 'row = getRow(id, i)' to the mix it throws a ByRef mismatch error??
This is a great example as to why using Option Explicit is a great practice.
Add Option Explicit to the very top of your worksheet module, outside your macro.
This forces you to declare all variables used in your subs, and also can help catch typos in your variable names.
This would catch that i is not declared. What's therefore happening is VBA/Excel by default will set i to be Variant.
Then, when you hit row = getRow(id, i), you're basically passing row = getRow([integer], [Variant]). But that sub is expecting getRow([integer],[integer])...hence your Type Mismatch error.
So, as mentioned, just do Dim i as Integer at the top of the ChangeRow sub.
(or, for long run VBA use Long instead of Integer).
In the function, you need to declare "Sheet" as Worksheet object
Function getRow(id As Integer, Sheet As Worksheet) As Integer
Private Sub FillRow(programCell As Range, storedProgramCell As Range)
Dim counter As Integer
For counter = 3 To 9
Dim cellOffset As Integer
cellOffset = counter - 3
Dim currentStoredCell As Range
Set currentStoredCell = storedProgramCell.Offset(0, cellOffset)
Dim value As String
value = currentStoredCell.value
Dim currentTargetCell As Range
Set currentTargetCell = programCell.Offset(0, cellOffset)
MsgBox currentStoredCell.value 'Works correctly, prints correct value
currentTargetCell.value = value
Next counter
End Sub
The line:
currentTargetCell.value = value
causes the code to stop executing, with no error.
I added the expression to my watch list, then stepped through the routine. The expression was seen as a Boolean:
This makes me think the expression is being viewed as a comparison, and the program abruptly ends since the returned Boolean is not being stored or used anywhere. I wouldn't doubt if I were wrong though.
I'm new to VBA, struggling to debug my program, so please forgive me if this is a petty mistake. I couldn't find any sources online that explains this problem.
Replace your subroutine with following code:
Private Sub FillRow(Dst As Range, Src As Range)
Dim x As Integer
Dim v As Variant
Dim Srcx As Range
Dim Dstx As Range
Debug.Print "FillRow"
Debug.Print Src.Address
Debug.Print Dst.Address
Debug.Print "Loop"
For x = 0 To 6
Debug.Print x
Set Srcx = Src.Offset(0, x)
Debug.Print Srcx.Address
v = Srcx.Value
Debug.Print TypeName(v)
Set Dstx = Dst.Offset(0, x)
Debug.Print Dstx.Address
Dstx.Value = v
Next
Debug.Print "Completed"
End Sub
Run and post in your question Immediate window output.
Value is a reserved word, even if vba does not raise an error on this name, you should not use it. Name it something else. Also, try setting it as a variant.
This must seem like a terribly simple question, but I cannot figure out why my functions are ending unexpectedly on the Range.value = val call. Perhaps I am missing something very basic, but I have tested these out and each one of them are failing to resolve to anything and I don't know how to capture the error.
Here is the initial function:
Function incrementCount(upper As Range, Summed As Range, ParamArray sums() As Variant)
Dim deduct As Integer
Dim summation As Integer
Dim elem As Variant
Dim i As Long
Dim temp As Range
up = upper.Value
summation = Summed.Value
'Initialize the starting points of the increments
For i = LBound(sums) To UBound(sums)
MsgBox IsObject(sums(i)) 'Prints out as an true
MsgBox TypeName(sums(i)) 'Prints out as Rnage
MsgBox sums(i).Value 'Prints out as 0
Set temp = sums(i)
MsgBox temp.Value 'Prints out as 0
Set temp = Summed
MsgBox temp.Value 'Prints out as 1 (which is correct)
temp.value = 3 'Errors here
MsgBox temp.Value 'Never makes it to this line
sums(i).Value = 1 'I have also tried this with the same result
Next i
<more code that is never reached>
End Function
I am at my wits end. I have searched MSDN, stackoverflow, and all the many excel forums and all of them show setting values to a range like this. I have even separated the setting of a range value to a different function like this:
Function testsub(thecell As Range, thevalue As Integer)
thecell.value = thevalue
End Function
Ultimately i would like to be able to do something like discussed in this article where I loop over a random assortment of ranges and will increment them. Any help at all would be greatly appreciated.
You have not specified how IncrementCount() is being called.
If your function is being called from a worksheet cell, then it is "bombing out" at the correct line. A UDF called from a cell cannot modify the contents of other cells, it can only return a value.
I am trying to put the below formula into a UDF so that I can get a cumulative return when I aggregate monthly returns.
In excel the formula has to be recognized as an array so when I type it in I press Ctrl + Shift + Enter to get the {} brackets around the formula.
Does anyone know how to do this?
I want to be able to just type in returns_calc() and select the range that would fit into the returns variable below.
{=(PRODUCT(1+returns/100)-1)*100}
You can use the [ ] notation in Application.Evaluate to calculate Array Formulas in VBA. Your above formula can be called in VBA in just 1 line as shown below
Sub Sample()
MsgBox Application.Evaluate(["=(PRODUCT(1+returns/100)-1)*100"])
End Sub
Now modifying it to accept a range in a function, you may do this as well
Function returns_calc(rng As Range) As Variant
On Error GoTo Whoa
Dim frmulaStr As String
frmulaStr = "=(PRODUCT(1+(" & rng.Address & ")/100)-1)*100"
returns_calc = Application.Evaluate([frmulaStr])
Exit Function
Whoa:
returns_calc = "Please check formula string" 'or simply returns_calc = ""
End Function
EXAMPLE SCREENSHOT
Something like this
Public Function Range_Product(theRange As Variant)
Dim var As Variant
Dim j As Long
var = theRange.Value2
Range_Product = 1#
For j = LBound(var) To UBound(var)
Range_Product = Range_Product * (1 + var(j, 1) / 100)
Next j
Range_Product = (Range_Product - 1) * 100
End Function
I have a list of keywords and want to see if one cell contains any one of these words. For example if my list of keywords is (Cat, Dog, Turtle) the function would return MATCH if it was looking inside "Mr. Dogs Magic Land". I have found a good UDF online to use as the function but when I try to loop it so it tests every word on my keyword list I get #VALUE!. The first function is my loop while the second is the UDF match function found on the internet (sorry don't remember where but props to whoever made it.) I've tried variations of word match functions such as InStr to no avail.
Function StringFind(rng(), source)
For I = LBound(rng) To UBound(rng)
StringFind = MyMatch(rng(I), source)
If StringFind = "MATCH" Then Exit Function
Next I
StringFind = "NO MATCH"
End Function
Function MyMatch(FindText As String, WithinText As Variant) As String
'
Dim vntFind As Variant
Dim vntWithin As Variant
For Each vntFind In Split(UCase(FindText), " ")
If Len(Trim(vntFind)) > 0 Then
For Each vntWithin In Split(UCase(WithinText), " ")
If Len(Trim(vntWithin)) > 0 Then
If vntFind = vntWithin Then
MyMatch = "MATCH"
Exit Function
End If
End If
Next
End If
Next
MyMatch = "NO MATCH"
End Function
1) FORMULA
I would first offer the non-VBA solution to this particular problem since VBA isn't really needed. This array formula will do the same thing. Enter the array by pressing CTRL-SHIFT-ENTER, you'll see the curly braces { } appear around your formula. Then you can copy down.
'=IF(OR(ISNUMBER(SEARCH($F$1:$F$3, A1))), "Match", "No Match")
2) UDF
Using the same syntax as yours, here's how I might approach this with a UDF.
Function MySearch(MyRNG As Range, MyStr As String) As String
Dim cell As Range
For Each cell In MyRNG
If LCase(MyStr) Like LCase("*" & cell & "*") Then
MySearch = "Match"
Exit Function
End If
Next cell
MySearch = "No Match"
End Function
Plugged this in as-is in my VBE, and I couldn't even compile.
This line
StringFind = MyMatch(rng(I), source)
needs to be changed to
StringFind = MyMatch(rng(I).Value, source)
to even get it to work for me. This MAY be the cause of your problem.
EDIT
Ok, I reviewed all in more detail. It looks like this will work for you. (Sorry, I didn't mean to just do it all for you, but here it is.) It probably needs some more tweaking to make it work for your needs.
The problem was that you were looking for undefined data types (added/changed main function call to As String and As Range). While the undefined types can work, I think it was confusing in seeing why the problem was coming up. I tried to set a breakpoint in the function and never even got that far because the wrong data type was being passed. Personally, I always use Option Explicit to help prevent issues like this from arising in my own code.
The below code will now look for the value in the first argument(Search, can be a "" text/String or a single cell/Range) against all the values in the second argument (Source a Range consisting of either a single or multiple cells).
Public Function StringFind(Search As String, Source As Range)
Dim rngCell As Range
For Each rngCell In Source.Cells
StringFind = MyMatch(Search, rngCell.Value)
If StringFind = "MATCH" Then Exit Function
Next rngCell
StringFind = "NO MATCH"
End Function
Function MyMatch(FindText As String, WithinText As Variant) As String
'
Dim vntFind As Variant
For Each vntFind In Split(UCase(FindText), " ")
If Len(Trim(vntFind)) > 0 Then
If vntFind = Trim(UCase(WithinText)) Then
MyMatch = "MATCH"
Exit Function
End If
End If
Next
MyMatch = "NO MATCH"
End Function