How to implement 'sumifs' in evaluate-function - excel

I have found a macro to evaluate strings. It is working fine with most formulas. But it wont evalute my sumifs-formulas.
The VBA for WM_Eval() goes like this:
Function wm_Eval(myFormula As String, ParamArray variablesAndValues() As
Variant) As Variant
Dim i As Long
'
' replace strings by values
'
For i = LBound(variablesAndValues) To UBound(variablesAndValues) Step 2
myFormula = RegExpReplaceWord(myFormula, variablesAndValues(i),
variablesAndValues(i + 1))
Next
'
' internationalisation
'
myFormula = Replace(myFormula, Application.ThousandsSeparator, "")
myFormula = Replace(myFormula, Application.DecimalSeparator, ".")
myFormula = Replace(myFormula,
Application.International(xlListSeparator), ",")
'
' return value
'
wm_Eval = Application.Evaluate(myFormula)
End Function
If I type wm_Eval("1+1") it works like a charm. But if I do:
="sumifs(b2:b10,a2:a10,"&D2&">=2"&D2&")" where d2=" it returns #Value.
So the formula it should evaluate would be: sumifs(b2:b10,a2:a10,">=2")
I am accustomed to danish excel - so it might just be something very very simple I am missing.

I feel you should give some more information about why you need this function, as I'm sure there'd be a better of way of achieving the task than the code above.
There are a number of reasons the function is returning an error. The main one is that your formula syntax isn't right. The wm_Eval() function requires a formula argument and then pairs of variable and value parameters. You haven't included the code for your RegExpReplaceWord() function, but I'd imagine it runs some kind of replacement of values for pseudo formula variables. Without seeing that part of your code, it's difficult to guess at the syntax, but a mock-up of the principle would be something like:
=wm_Eval("SUMIFS(A1:A4,B1:B4,{a})","{a}",">=1")
where {a} is replaced by ">=1"
Data type is also an issue, as you can see in the following syntax:
=wm_Eval("SUMIFS(A1:A4,B1:B4,{a})","{a}",1)
I don't want to be too brutal, but this code is pretty poorly constructed and there's no way that it'll be able to parse all the different formulae with various passed in parameters. Yes it works for SUM but that's because you don't pass in any variable-value pairs. I would steer away from this solution and come up with something more robust. If for example you want to gather string versions of your formula, why not just pass in the range containing the formula and deal with it from there?
I can tell you that I have written a routine to parse values from formulas. It is long and complex and took many, many hours to develop including some WINAPI calls and hooks, so you have a lot of work ahead of you if you wish to continue with your current approach.
However, I guess I ought to show you how your current function works, so I've mocked-up a RegExpReplaceWord() function and commented out that internationalisation code:
Public Function wm_Eval(myFormula As String, ParamArray variablesAndValues() As Variant) As Variant
Dim i As Long
'
' replace strings by values
'
For i = LBound(variablesAndValues) To UBound(variablesAndValues) Step 2
myFormula = RegExpReplaceWord(myFormula, variablesAndValues(i), variablesAndValues(i + 1))
Next
'
' internationalisation
'
' myFormula = Replace(myFormula, Application.ThousandsSeparator, "")
' myFormula = Replace(myFormula, Application.DecimalSeparator, ".")
' myFormula = Replace(myFormula, Application.International(xlListSeparator), ",")
'
' return value
'
wm_Eval = Application.Evaluate(myFormula)
End Function
Private Function RegExpReplaceWord(myFormula As String, variable As Variant, value As Variant) As String
If VarType(value) = vbString Then
value = """" & value & """"
End If
RegExpReplaceWord = Replace(myFormula, variable, value)
End Function
So in your worksheet cell, you can add the functions as shown in the quote blocks above.
In summary, I wouldn't continue with your current approach or you will be plagued with so many errors that your lost 3 days will seem like nothing.

Related

Function to get end of xlFillSeries vba

I'm trying to build a function to return the end string/cell value of an xlFillSeries. Is there any way to do it without actually writing to the worksheet and then selecting last cell? I want to avoid manipulating the worksheet/workbook
.
Here is the code to generate the series:
Dim SeqStart As String, SeqInt As Integer
SeqStart = "XYZ100"
SeqInt = 42 ' Function should return XYZ141
With Range("A1")
.Value = UCase(SeqStart)
If SeqInt > 1 Then
.AutoFill Destination:=Range("A1").Resize(SeqInt), Type:=xlFillSeries ' Will cause error if only 1 sample sequence
End If
End With
I want to utilize XlFillSeries as it handles odd data well, Eg: If my SeqStart = A1B100 then I can't utilize Regex to strip this down to just numbers, perform math, and then put it back together as there is a B in the middle of the string. I do know the series will/should always end in numbers, but I've struggled a bit to strip just the numeric portion from the right hand of the string without knowing string length and or mix of alphanumeric oddities.
So I guess my question could be answered by figuring out how to strip numbers from right side of string and then doing math and putting string back together. The numbers are what increments.
Or, I would just utilize XlFillSeries but without actually writing to the workbook. Currently I did just set it up so that it writes to a "temp sheet" and then captures the last cell and deletes the temp sheet, but I wondered if there was a better way.
Specifically talking about XlFillSeries there is no way to utilize it without writing to a Range which must be "real cells". Therefor the best method is to utilize a "temp sheet" and return the last cell. It may be possible to utilize functions to strip numbers from right hand side of string and then re-build string, but I trust XlFillSeries more and have resigned to just using a "temp sheet".
Here is my function:
Note: There are some custom functions that I wont include, but you can guess what they do based on the name!
Public Function EndOfXlFillSeries(SeqStart As String, SeqInt As Integer) As String
Dim DestSheet As Worksheet
WorksheetCreateDelIfExists ("XLFillSeriesTmp")
Set DestSheet = Worksheets("XLFillSeriesTmp")
With DestSheet.Range("A1")
.Value = UCase(SeqStart)
If SeqInt > 1 Then
.AutoFill Destination:=DestSheet.Range("A1").Resize(SeqInt), Type:=xlFillSeries ' Will cause error if only 1 SeqInt
Else
EndOfXlFillSeries = SeqStart
End If
End With
Dim lRow As Integer
lRow = lRowOfCol(1)
EndOfXlFillSeries = DestSheet.Range("A" & lRow).Value
Call WorksheetDelete(DestSheet)
End Function

Creating a function with unknown number of inputs

I'm trying to create a VBA-function in Excel where the number of inputs are unknown.
The inputs are all going to be numbers.
I've found information about Paramarray, but don't know it thats a good choice, and I am not able to make it work.
You stated that
I am not able to make it work.
However, you:
Have not posted the problematic code
have not posted your inputs
have not posted your expected results
have not posted your actual results
Given this lack of information, it is difficult to pinpoint your specific problem(s). Here is an example of a working function with variable number of inputs. Its purpose is to display for debugging, the values of several variables:
Public Function Displayy(ParamArray s()) As String
Dim i As Long
Displayy = CStr(s(0))
If UBound(s) = 0 Then Exit Function
For i = 1 To UBound(s)
Displayy = Displayy & vbCrLf & CStr(s(i))
Next i
End Function
Since your are building function, I am assuming the numbers are placed in Excel range (Example: A1: A10)
So you can use range as argument
Function YourFunctionName(rng as Range) as integer/Float/....etc
I would use variant. It can be range or array of numbers.
Public Function DisplayZ(Inputs As Variant) As String
Dim i As Long
Dim v As Variant
For Each v In Inputs
DisplayZ = DisplayZ & vbCrLf & v
Next v
End Function

Using Excel Proper Function with exception | Excel

Essentially I have multiple strings within my Excel Spreadsheet that are structured the following way:
JOHN-MD-HOPKINS
REC-PW-RESIN
I would like to use the proper function but exclude the part of the string that is within the dashes (-).
The end result should look like the following:
John-MD-Hopkins
Rec-PW-Resin
Is there an excel formula that is capable of doing this?
You may need to create your own VBA function to do this, that checks if there are two hyphens in the data, and if so converts the first and last words to proper case without touching the middle word, otherwise just converts the string to proper case.
Paste the following into a module within Excel:
Function fProperCase(strData As String) As String
Dim aData() As String
aData() = Split(strData, "-")
If UBound(aData) - LBound(aData) = 2 Then ' has two hyphens in the original data
fProperCase = StrConv(aData(LBound(aData)), vbProperCase) & "-" & aData(LBound(aData) + 1) & "-" & StrConv(aData(UBound(aData)), vbProperCase)
Else ' just do a normal string conversion to proper case
fProperCase = StrConv(strData, vbProperCase)
End If
End Function
Then, in your worksheet, you can use this just as you would any built-in formula, so if "JOHN-MD-HOPKINS" is in cell A1, you would use this as a formula in another cell:
=fProperCase(A1)
Which would display John-MD-Hopkins as required.
EDITED CODE
As the requirement is to leave the second word, then this modified VBA function, which "walks" the array should work instead:
Function fProperCase2(strData As String) As String
Dim aData() As String
Dim lngLoop1 As Long
aData() = Split(strData, "-")
For lngLoop1 = LBound(aData) To UBound(aData)
If (lngLoop1 = LBound(aData) + 1) And (lngLoop1 <> UBound(aData)) Then
aData(lngLoop1) = aData(lngLoop1)
Else
aData(lngLoop1) = StrConv(aData(lngLoop1), vbProperCase)
End If
Next lngLoop1
fProperCase2 = Join(aData, "-")
End Function
It basically looks to see if the array element being dealt with is the second (lngLoop1=LBound(aData)+1) and also not the last (lngLoop1<>UBound(aData)).
Regards,

excel vba - convert iferror/find formula to vba

I have the following excel formula
IFERROR(MID(B7;FIND(" ";B7)+1;1);"")
I want to convert this now to VBA, but I have trouble integrating the Find function here. This is what I have so far
IfError(Mid(Sheets("MySheet").Cells(x, 7),Sheets("MySheet").Cells(x, 7).Find " " + 1, 1), "")
But that's clearly not correct. Can somebody help me with this?
Something like so, called like get_first_char_after_space("testing xys")
Function get_first_char_after_space(strInput As String) As String
If InStr(1, strInput, Chr(32)) > 0 Then
get_first_char_after_space = Left(Split(strInput, Chr(32))(1), 1)
Else
get_first_char_after_space = vbNullString
End If
End Function
You can find the solution for IFERROR at the following SO question: https://stackoverflow.com/a/29390944/6908282.
And VBA has a Range.Find menthod which you can use to replaicate FIND formula.
Documentation link: https://learn.microsoft.com/en-us/office/vba/api/excel.range.find
Here is a UDF to do the job of your worksheet function. As you see, an error can be avoided in VBA. I presume that the entire function could be compressed into one line but whatever benefit that might entail would come at the cost of readability and possibly stability. As it is, the code is very robust. It easily transplants into a bigger project as, no doubt, is your intention.
Function NextChar(Cell As Range) As String
' IFERROR(MID(B7;FIND(" ";B7)+1;1);"")
Dim Txt As String
Dim n As Integer
Txt = Cell.Value
n = InStr(Txt, " ")
If n Then NextChar = Mid(Txt, n + 1, 1)
End Function
If called from the worksheet the function would be =NextChar(B7). Called from code it would be Debug.Print NextChar(Sheet1.Cells(7, 2))

Manipulating Ranges in Excel - Returning a Value (Error 2029)

I am a quite new to Excel VBA, and I come from a more... traditional programming background (Java, C). I am having issues with passing a Range Object as a parameter in my User-defined function (see below). The idea of my function is to take several parameters to complete a VLOOKUP of a filtered range.
I may have several syntax issues (I am unsure of my return type and my usage of VLOOKUP), and I would appreciate some guidance on this. See results, more information in my code:
Public Function GETVALUE(screen As String, strEvent As String, dataRange As Range, strDate As String) As String
'ASSUMPTION: dataRange has three columns; first column contains lookup values; Second
' column contains dates for filtering; Third column contains return values
Dim result As String
'remove irrelevant dates in dataRange; apply filter
'ASSUMPTION: This process should return a Range that is removes all Rows that does
'not have strDate in the second column
Dim newRange As Range
'RESULT: Returns #VALUE!. I know this is not the typical := syntax I see in many
'examples but this one apparently compiles, so I use it. I comment this line out
'and try to make the other lines below work with dummy parameters or fixed ranges
newRange = dataRange.AutoFilter(2, strDate)
'Now I try to use the newly filtered, "newRange" and use that in my VLOOKUP
'and return it.
result = [VLOOKUP("*" & screen & "/" & strEvent & "*", newRange, 3, False)]
'I can see an Error 2029 here on Result
GETVALUE = result
'RESULT: Returns #VALUE!
End Function
VLOOKUP ignores any filtering of your data. In other words VLOOKUP will also look in the hidden rows.
I would suggest two alternative approaches:
Copy the visible cells of the filtered range to a new sheet and perform the lookup there:
Set newRange = dataRange.AutoFilter(2, strDate).SpecialCells(xlCellTypeVisible)
set ws = worksheets.Add
ws.Range("A1").Resize(newRange.Rows.Count,newRange.Columns.Count).Value = newRange.Value
etc.
Note that this can not be done in a UDF, you would have to do it in a a Sub.
Store the values in dataRange in a variant array and loop to search for the required value:
Dim arr() as Variant
arr = dataRange.Value
For i = LBound(arr,1) to UBound(arr,1)
If (arr(i,2) = strDate) And (arr(i,1) LIKE "*" & screen & "/" & strEvent & "*"( Then
GETVALUE = arr(i,3)
Exit Function
End If
Next
This I think causes your problem:
result = [VLOOKUP("*" & screen & "/" & strEvent & "*", newRange, 3, False)]
Replace it with this instead:
result = Evaluate("VLOOKUP(*" & screen & "/" & strEvent _
& "*, " & newRange.Address & ", 3, False)")
[] which is shortcut for Evaluate doesn't work on variables.
If it is a direct VLOOKUP like below:
result = [VLOOKUP(D1,Sheet1!$A:$C,3,FALSE)]
it will work. But if you are working with variables as in your example, you have to explicitly state it.
And take note that Evaluate accepts Name argument in a form of string.
So you simply have to concatenate all your strings and then explicitly use Evaluate.
Edit1: Additional Inputs
This will not work as well: newRange = dataRange.AutoFilter(2, strDate).
To pass Objects to a Variable you need to use Set like this.
Set newrange = dataRange.AutoFilter(2, strDate)
On the other hand, AutoFilter method although returning a Range Object fails.
I'm not entirely sure if this can't really be done.
Moving forward, to make your code work, I guess you have to write it this way:
Edit2: Function procedures only returns values, not execute methods
Public Function GETVALUE(screen As String, strEvent As String, rng As Range)
GETVALUE = Evaluate("VLOOKUP(*" & screen & "/" & strEvent & "*, " _
& rng.Address & ", 3, False)")
End Function
To get what you want, use above function in a Sub Procedure.
Sub Test()
Dim dataRange As Range, strDate As String, myresult As String
Set dataRange = Sheet2.Range("A2:E65") 'Assuming Sheet2 as property name.
strDate = "WhateverDateString"
dataRange.AutoFilter 2, strDate
myresult = GETVALUE("String1", "String2", dataRange)
End Sub
Btw, for a faster and less complex way of doing this, try what Portland posted.
Basically you must write :
Getvalue = Application.VLookup( StringVar, RangeVar, ColumnNumberVar)
Vlookup needs your data to be previously ordered in alphabetical order, or it doesn't work.
Excel Developers's approach is a good one too, using a VBA Array.
I can also point the VBA functions FIND, and MATCH, wich will get you the row of searched data, and then you can pull what you need from the 3rd column of that row.
Wichever is faster depends of the size of your range.

Resources