How to pass a date as an argument in an Excel VBA function - vba

I'm trying to make a function that will take a date in a cell as an argument, then use that date to lookup a value. The date to be passed will be in the variable EffDate. Then the function should go to the worksheet RateChgs, check the NewPymtEffDateRange for the EffDate, and, upon finding it, go to the EscrowPymtAmtRange (one column wide) and return the value on the same row there.
I've only gotten to the point of testing it in the immediate window by typing GetEscrowPymt(8/1/2000) (or some other date). From the value of the Position variable, I can tell that the function isn't finding the date even though it's there. Is this a problem with how I'm passing the date?
Function GetEscrowPymt(EffDate As Date)
Dim PymtEffDateRange As Range
Dim EscrowPymtAmtRange As Range
Dim Position As Integer
Set PymtEffDateRange = Worksheets("RateChgs").Range("NewPymtEffDate")
Set EscrowPymtAmtRange = Worksheets("RateChgs").Range("EscrowPymt")
Position = Application.WorksheetFunction.Match(EffDate, PymtEffDateRange, 1)
MsgBox (Position)
End Function

The last argument in the Match function allows for returning an approximate match. If you require an exact match, then you should use the last argument of 0 to require an exact match. Otherwise, using the arguments 1 or -1 will return approximate match and assume also that the data is sorted ascending.
Position = Application.WorksheetFunction.Match(EffDate, PymtEffDateRange, 0)
The Match function will error if the effDate value is not found in the lookup array, so you may need error handling logic to account for that possibility. I would probably use the Application.Match function which can accept an error type, where the Match function in the Worksheet class will only accept long/integer values and will raise an error if the value isn't found:
Dim Position as Variant
Position = Application.Match(EffDate, PymtEffDateRange, 0)
If IsError(Position) Then
MsgBox EffDate & " not found!", vbInformation
Exit Function
' -- OR --
' assign some other return value for the function, etc.
End If
Some functions also have difficulty working with date values, so let me know if that doesn't solve the issue.
VBA also doesn't play well with various system locales, if you're expecting "8/1/2000" to be anything other than August 1, 2000, you may have more problems since VBA will interpret that by the US date format, not the system locale (e.g., in the UK that date would be 8 January, 2000). In that case, it may be best to treat the date as text and do a match based on text rather than date.

Related

Variable in Dlookup in Access 2016 returns runtime error 13 - type mismatch

The logic isn't complex. The application uses a temporary table into which data for a report is appended. Once the report has been run, the data is cleared. Initially, the client told me that he would enter all the data (by taking attendance of people attending an event) would be done at once. Now, that has changed. I lookup the max ID in the temp table - if the number of rows is greater than 0. I then want to use dlookup to let the client know where she left off lest they create more problems.
Here's the logic:
Dim VMax As Variant
Dim VNFind As String
VMax = DMax("foodpantryid", "signin_sheet_data") 'This works correctly
MsgBox VMax 'This works correctly
VNFind = DLookup("last_name", "Dat_household_member", "[household_id]" = VMax) 'I get the type mismatch/error 13 here.
MsgBox VNFind 'so this never works
Additional information:
Household_id is a long integer. When I change it to integer, I get an error 94 (invalid use of null).
I have tried setting VMax to variant, integer, and long. Still no success. I would think variant would have worked.
The DLookup works if I don't include "[household_id]" = VMax.
I've tried including the =VMax in "" and that fails as well. That produces a 2471 error.
You've put your = outside the string delimiters.
This means: say your ID is 5. Then the parameter is "[household_id]" = 5. That's a comparison between the string "[household_id]" and the number 5, which causes a type mismatch, because you can't compare strings to numbers unless the string can be cast to a number.
Instead, you should include the = in your string, and the DLookUp should be:
DLookup("last_name", "Dat_household_member", "[household_id] = " & VMax)
Or, better yet, use parameters. This avoids most typecasting and string concatenation errors.
TempVars!VMax = VMax
DLookup("last_name", "Dat_household_member", "[household_id] = TempVars!VMax")
TempVars.Remove "VMax"

Finding and return values with associated rows on excel on multiple pages

Thanks in advance for any help! I haven't used much VBA in excel and can't work out how to do what I need, however I believe I need it to achieve the function I need within the workbook.
I have 31 data pages, in which I need to find if certain information is present and display it on a summary page.
I need is to check if there are values in the column AQ, If there is then I need the data returned in that row in columns E, F and G.
There could be multiple instances per sheet or none per sheet.
Hopefully this will example explain it better:
E F G ... AQ
Date Name Location Exception
2 1-12-17 Dave England
3 1-12-17 Sarah Wales Exp
In the example data above the information I would want returned on the Summary page is from row 3. (This type of data is on each of the 31 other pages)
Hope this makes sense! Any help on how to do this would be greatly appreciated.
There are a number of different ways you could tackle this problem, for example, pivot tables with specific filter conditions, a UDF that finds the matches and prints them to the output you'd like, etc. In general, it's not a bad idea to use the Range.Find method and loop through all the matches.
This requires a certain amount of programming time and energy, which not everyone has, although most people who use Excel a lot eventually end up using vLookup a lot. I've always been unsatisfied with vLookup, it's so limited compared to the vba Range.Find method. Just for you, since it's almost Christmas and I ran out of work that I'm actually paid to do, here's a little gem that should help solve your problem.
It's a UDF lookup that allows you specify which number match to return, and return a custom offset in rows or column to retrieve as a value. Incrementing the variable matchNum will give you all the matches in the range, and you can return whatever columns you want using the appropriate amount of offset.
The use of the Range.Find method should give you an idea of how you could use code to populate a worksheet with exactly what you wanted without using a UDF lookup function. I'll leave that as an exercise for the reader.
'################################################################################################################################
' findwhat: the value you want to find. (needle)
' where: the range you want to look for findwhat (haystack)
' matchNum: if the needle is found more than once in the haystack, find the nth target.
' rowoffset: offset your return value from the target, positive will return the value of the cell below the target.
' columoffset: offset your return value from the target, positive will return the value of the cell to the right of the target.
'################################################################################################################################
Public Function powerLookup(findwhat As Variant, where As Range, Optional matchNum As Long = 1, Optional rowOffset As Long = 0, Optional columnOffset As Long = 0) As Variant
Dim rngResult As Range, firstAddress As String
Set rngResult = Nothing
On Error GoTo Errorhandler ' if something breaks return #NA (will definitely happen if first find call fails)
Do While matchNum > 0 'loop through the matches until there are no matches or we've reached the target matchnumber
'the first time, rngResult will be Nothing, so the first find can't have rngResult as an input.
With where
If rngResult Is Nothing Then
Set rngResult = .find(findwhat, , xlValues)
firstAddress = rngResult.Address 'keep this to know if we've looped past the start
Else
'if rngResult is not nothing we've already found a match, find the next match until matchnum is 0
Set rngResult = .find(findwhat, rngResult, xlValues)
If rngResult.Address = firstAddress Then
'if we reach the first address we've looped around, no more matches found, throw #NA error
powerLookup = CVErr(xlErrNA)
Exit Function
End If
End If
End With
matchNum = matchNum - 1
Loop
powerLookup = rngResult.offset(rowOffset, columnOffset).Value 'offset the output
Exit Function
Errorhandler:
powerLookup = CVErr(xlErrNA)
End Function

What am I missing in this IsEmpty clause?

In an Excel VBA module I'm building, I have some code to execute if a table of out-of-gauge cargo contains anything. I initially wrote this:
If Not IsEmpty(Range("OOGData")) Then
...
Else
...
End If
But even when OOGData is empty, it keeps returning False. I've tried it with If IsEmpty(Range("OOGData")) = False Then` but that doesn't seem to make any difference. My current code is
If IsEmpty(Range("OOGData")) = False Then
...but that still activates with the empty range.
I've made sure there are no formulae, hidden values or anything that could be showing up.
Any idea what the problem could be?
According to this information:
Returns a Boolean value indicating whether a variable has been
initialized.
In your code you are not working with variable therefore you shouldn't expect correct value.
When checking single cell you should use Len() function instead:
If Len(Range("OOGData"))=0 Then
'cell is empty
When checking if range of cells is empty use this solution:
If WorksheetFunction.CountA(Range("OOGData"))=0 Then
'rabge is empty
The final alternative I can think of is to use loops.
I've decided to cheat. Instead of checking the list there, I've added a Boolean variable (bContainsOOG) that's set to True any time an OOG cargo item is added to the OOG list, and then the reporting sub checks against that instead. But thanks both of you for those suggestions, they'll come in handy in another bit I was getting stuck on. :-)
This is a function that I thinks very closely fits:-
' Function to determine the last (first blank/null/empty) cell in a series of data in either a column
' or row
' Usage :- DataSeriesEnd(w,r,b)
' where w is the name of the worksheet.
' r is the row or column as an integer
' b is a boolean (true/false) if true then processing a column, if false a row
Public Function DataSeriesEnd(Worksheet As String, rc_index As Integer, bycolumn As Boolean) As Integer
Dim cv As Variant
For DataSeriesEnd = 1 To 500
If bycolumn Then
cv = Worksheets(Worksheet).Cells(DataSeriesEnd, rc_index)
Else
cv = Worksheets(Worksheet).Cells(rc_index, DataSeriesEnd)
End If
If IsNull(cv) Or IsEmpty(cv) Or cv = "" Then Exit For
Next DataSeriesEnd
' Position to previous cell (i.e. this one is empty)
DataSeriesEnd = DataSeriesEnd - 1
End Function
Note! There is a limit of 500 rows/columns

How do I get a function to refer to the cell it is residing in?

I am writing a function and I need the function to refer to a cell offset from where it resides in. I've got rows of data with each column containing a specific variable. The function resides in the last column, and is supposed to check if each variable defined in it matches the variables in the row preceding it. Each match is supposed to score a +1 to the final value of the function. Here's a short version of my function.
Public Function cellscore(testvar1)
totalscore = 0
If testvar1= activecell.Offset(-1, -10) Then
cellscore = totalscore + 1
End If
End Function
What do I replace activecell with, so that the function runs correctly for each row of data that I have?
I'm new at VBA so please bear with me if this is a simple question. Thanks in advance.
The easiest way would be to pass a numeric value to the function which represents the row number. This means that your code would become something like:
Public Function cellscore(testvar1,RowNum as Long)
totalscore = 0
If testvar1= Range("SOMECELL").Offset(RowNum-2, -10) Then
cellscore = totalscore + 1
End If
End Function
You will need to change SOMECELL to represent the cell in the first row. For example if your formula is placed in the T column, then you would replace SOMECELL with T1.
The clever part now comes with how you call the function within the worksheet. You do this by using the formula:
=cellscore(SOMEVALUE,ROW())
Obviously replace SOMEVALUE with the value you are testing for. Now regardless of which row this formula is placed it will reference the current one when calling the function. Hope this helps!

#VALUE error with Excel VBA Function

In my Excel spreadsheet I have two columns.
A contains strings with the values 'Yes', 'No' or 'Maybe'.
B contains strings with a year in.
I need a function to determine the number of occurrences of a year in column B, where the equivalent value in column A is 'Yes'.
I currently have the following code:
Function CountIfYearAndValue(Rng As Range, YNM As String, Year As String) As Integer
Dim count As Integer
count = 0
For Each c In Rng.Cells
If (StrComp(Abs(c.Value), Year, vbTextCompare) = 0) And (StrComp(Cells(c.Row, A), YMN, vbTextCompare) = 0) Then count = count + 1
Next
CountIfYearAndValue = count
End Function
The idea of this code is that we iterate through every cell in the range given (a range on column B) and check if the year is equal to the Year parameter. And if the equivalent cell on column A is equal to the YNM parameter we increment the count variable.
For some reason this code does not work when I use the following parameter:
=CountIfYearAndValue('Years'!B1:B7,"Yes","Year 7")
It just does the #VALUE error and refuses to display any outcome.
Any help would be much appreciated.
Edit: All of the values in both cells are on of an unformatted datatype ('General') and no cells are blank.
It sounds like you are reinventing the wheel... There already is a built in function (advantage: being much faster than a UDF) that does exactly what you are after. It is called COUNTIFS()
All YESes for Year 7 in rows 1 to 10.
=COUNTIFS(B1:B10, "Year 7",A1:A10, "Yes")
I just had a quick look at your code and I think there are possibly a few reasons why your original code is not working as expected.
YNM is a valid column name therefore it should not be used as a variable name. You should avoid naming your variables like that - give it a more meaningful name
YNM != YMN as you had it in your code (see function definition and then the misspelled version in the StrComp() function)
Year is a valid VBA built in function, therefore once again you should avoid using it as a variable name as you're exposing yourself to a naming collision.
Add Option Explicit at the top of your module. This requires you to Dimension all you variables. It's always recommended for many many reasons.
rng variable is of Range type therefore you do not need to explicitly add the .Cells property to it. Even though it may help in some cases - at a bit more advanced level you may face some runtime type compatibility issues. ( runtime may convert your rng Range variable to a 2D array etc )
Added an explicit conversion in the second StrComp() function around the c.Offset(0, -1) as you don't want the runtime to (rare but still possible) convert your Yes to a Boolean data type. Explicit conversion to a String just gives you that extra protection ;p (lol)
therefore, something like this returns the correct value
Function CountIfYearAndValue(rng As Range, choice As String, myYear As String) As Long
Dim count As Long
count = 0
Dim c As Range
For Each c In rng
If (StrComp(c, myYear, vbTextCompare) = 0) And (StrComp(CStr(c.Offset(0, -1)), choice, vbTextCompare) = 0) Then
count = count + 1
End If
Next c
CountIfYearAndValue = count
End Function
Right, I hope this helps you understand bits and pieces :) any questions please leave a comment